diff --git a/.cirrus.yml b/.cirrus.yml index 98cd6276e0e6..453a2ce0ac2c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -132,6 +132,7 @@ task: osx_instance: image: catalina-xcode-11.3.1-flutter upgrade_script: + - sudo gem install cocoapods - flutter channel stable - flutter upgrade - flutter channel master @@ -190,6 +191,7 @@ task: setup_script: - flutter config --enable-macos-desktop upgrade_script: + - sudo gem install cocoapods - flutter channel master - flutter upgrade - git fetch origin master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b278b43e524f..b763320b67c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ [![Build Status](https://api.cirrus-ci.com/github/flutter/plugins.svg)](https://cirrus-ci.com/github/flutter/plugins/master) -_See also: [Flutter's code of conduct](https://flutter.io/design-principles/#code-of-conduct)_ +_See also: [Flutter's code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md)_ ## Things you will need @@ -131,9 +131,8 @@ pub global run flutter_plugin_tools xctest --target RunnerUITests --skip ` * Hack away. - * Verify changes with [flutter_plugin_tools](https://pub.dartlang.org/packages/flutter_plugin_tools) + * Verify changes with [flutter_plugin_tools](https://pub.dev/packages/flutter_plugin_tools) ``` pub global activate flutter_plugin_tools pub global run flutter_plugin_tools format --plugins plugin_name diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index 023a140fbcc9..1b6a7b749e66 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.4.5+19 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 0.4.5+18 + +* Update Flutter SDK constraint. + ## 0.4.5+17 * Update Dart SDK constraint in example. diff --git a/packages/android_alarm_manager/README.md b/packages/android_alarm_manager/README.md index 9d245e5edc20..cf02bf66ff11 100644 --- a/packages/android_alarm_manager/README.md +++ b/packages/android_alarm_manager/README.md @@ -1,6 +1,6 @@ # android_alarm_manager -[![pub package](https://img.shields.io/pub/v/android_alarm_manager.svg)](https://pub.dartlang.org/packages/android_alarm_manager) +[![pub package](https://img.shields.io/pub/v/android_alarm_manager.svg)](https://pub.dev/packages/android_alarm_manager) A Flutter plugin for accessing the Android AlarmManager service, and running Dart code in the background when alarms fire. @@ -121,6 +121,6 @@ register plugins. This can be resolved by running `flutter upgrade` to upgrade to the latest Flutter version.** For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 9de2d8308419..b055823c2db3 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -4,7 +4,7 @@ description: Flutter plugin for accessing the Android AlarmManager service, and # 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.4.5+17 +version: 0.4.5+19 homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager dependencies: @@ -25,4 +25,4 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index 5a3ba03b33eb..113e4464c947 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.0.0-nullsafety.1 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 0.3.7+8 + +* Update Flutter SDK constraint. + ## 0.3.7+7 * Update Dart SDK constraint in example. diff --git a/packages/android_intent/README.md b/packages/android_intent/README.md index f7dfdfed9860..aabf059ea336 100644 --- a/packages/android_intent/README.md +++ b/packages/android_intent/README.md @@ -53,13 +53,13 @@ if (platform.isAndroid) { Feel free to add support for additional Android intents. The Dart values supported for the arguments parameter, and their corresponding -Android values, are listed [here](https://flutter.io/platform-channels/#codec). +Android values, are listed [here](https://flutter.dev/docs/development/platform-integration/platform-channels#codec). On the Android side, the arguments are used to populate an Android `Bundle` instance. This process currently restricts the use of lists to homogeneous lists of integers or strings. > Note that a similar method does not currently exist for iOS. Instead, the -[url_launcher](https://pub.dartlang.org/packages/url_launcher) plugin +[url_launcher](https://pub.dev/packages/url_launcher) plugin can be used for deep linking. Url launcher can also be used for creating ACTION_VIEW intents for Android, however this intent plugin also allows clients to set extra parameters for the intent. @@ -67,6 +67,6 @@ clients to set extra parameters for the intent. ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart index 9d701979b392..0ab2d7bee420 100644 --- a/packages/android_intent/lib/android_intent.dart +++ b/packages/android_intent/lib/android_intent.dart @@ -36,7 +36,7 @@ class AndroidIntent { this.arguments, this.package, this.componentName, - Platform platform, + Platform? platform, this.type, }) : assert(action != null || componentName != null, 'action or component (or both) must be specified'), @@ -47,8 +47,8 @@ class AndroidIntent { /// app code, it may break without warning. @visibleForTesting AndroidIntent.private({ - @required Platform platform, - @required MethodChannel channel, + required Platform platform, + required MethodChannel channel, this.action, this.flags, this.category, @@ -66,47 +66,47 @@ class AndroidIntent { /// includes constants like `ACTION_VIEW`. /// /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. - final String action; + final String? action; /// Constants that can be set on an intent to tweak how it is finally handled. /// Some of the constants are mirrored to Dart via [Flag]. /// /// See https://developer.android.com/reference/android/content/Intent.html#setFlags(int). - final List flags; + final List? flags; /// An optional additional constant qualifying the given [action]. /// /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. - final String category; + final String? category; /// The Uri that the [action] is pointed towards. /// /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. - final String data; + final String? data; /// The equivalent of `extras`, a generic `Bundle` of data that the Intent can /// carry. This is a slot for extraneous data that the listener may use. /// /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. - final Map arguments; + final Map? arguments; /// Sets the [data] to only resolve within this given package. /// /// See https://developer.android.com/reference/android/content/Intent.html#setPackage(java.lang.String). - final String package; + final String? package; /// Set the exact `ComponentName` that should handle the intent. If this is /// set [package] should also be non-null. /// /// See https://developer.android.com/reference/android/content/Intent.html#setComponent(android.content.ComponentName). - final String componentName; + final String? componentName; final MethodChannel _channel; final Platform _platform; /// Set an explicit MIME data type. /// /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. - final String type; + final String? type; bool _isPowerOfTwo(int x) { /* First x in the below expression is for the case when x is 0 */ @@ -146,17 +146,18 @@ class AndroidIntent { return false; } - return await _channel.invokeMethod( + final result = await _channel.invokeMethod( 'canResolveActivity', _buildArguments(), ); + return result!; } /// Constructs the map of arguments which is passed to the plugin. Map _buildArguments() { return { if (action != null) 'action': action, - if (flags != null) 'flags': convertFlags(flags), + if (flags != null) 'flags': convertFlags(flags!), if (category != null) 'category': category, if (data != null) 'data': data, if (arguments != null) 'arguments': arguments, diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index 8d41d5165c68..52928f08093f 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -1,10 +1,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -# 0.3.y+z is compatible with 1.0.0, if you land a breaking change bump -# the version to 2.0.0. -# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.3.7+7 +version: 2.0.0-nullsafety.1 flutter: plugin: @@ -16,15 +13,15 @@ flutter: dependencies: flutter: sdk: flutter - platform: ">=2.0.0 <4.0.0" - meta: ^1.0.5 + platform: ^3.0.0-nullsafety.4 + meta: ^1.3.0-nullsafety.6 dev_dependencies: - test: ^1.3.0 - mockito: ^3.0.0 + test: ^1.16.0-nullsafety.13 + mockito: ^4.1.3 flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.3.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/android_intent/test/android_intent_test.dart b/packages/android_intent/test/android_intent_test.dart index 311628853159..b0fa48e22fee 100644 --- a/packages/android_intent/test/android_intent_test.dart +++ b/packages/android_intent/test/android_intent_test.dart @@ -11,9 +11,11 @@ import 'package:platform/platform.dart'; void main() { AndroidIntent androidIntent; - MockMethodChannel mockChannel; + late MockMethodChannel mockChannel; setUp(() { mockChannel = MockMethodChannel(); + when(mockChannel.invokeMethod('canResolveActivity', any)) + .thenAnswer((realInvocation) async => true); }); group('AndroidIntent', () { diff --git a/packages/battery/battery/CHANGELOG.md b/packages/battery/battery/CHANGELOG.md index 260f3efeb8ad..c9268d4d4e3c 100644 --- a/packages/battery/battery/CHANGELOG.md +++ b/packages/battery/battery/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.10 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 1.0.9 + +* Update Flutter SDK constraint. + ## 1.0.8 * Update Dart SDK constraint in example. diff --git a/packages/battery/battery/README.md b/packages/battery/battery/README.md index 22ce5007acd7..358dee6f8f27 100644 --- a/packages/battery/battery/README.md +++ b/packages/battery/battery/README.md @@ -1,11 +1,11 @@ # Battery -[![pub package](https://img.shields.io/pub/v/battery.svg)](https://pub.dartlang.org/packages/battery) +[![pub package](https://img.shields.io/pub/v/battery.svg)](https://pub.dev/packages/battery) A Flutter plugin to access various information about the battery of the device the app is running on. ## Usage -To use this plugin, add `battery` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +To use this plugin, add `battery` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### Example diff --git a/packages/battery/battery/pubspec.yaml b/packages/battery/battery/pubspec.yaml index ec747190ac69..d3c823d73ad2 100644 --- a/packages/battery/battery/pubspec.yaml +++ b/packages/battery/battery/pubspec.yaml @@ -2,7 +2,7 @@ name: battery description: Flutter plugin for accessing information about the battery state (full, charging, discharging) on Android and iOS. homepage: https://github.com/flutter/plugins/tree/master/packages/battery/battery -version: 1.0.8 +version: 1.0.10 flutter: plugin: @@ -32,4 +32,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/battery/battery_platform_interface/CHANGELOG.md b/packages/battery/battery_platform_interface/CHANGELOG.md index 6fadda91b380..09ac38cc5b4b 100644 --- a/packages/battery/battery_platform_interface/CHANGELOG.md +++ b/packages/battery/battery_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +- Update Flutter SDK constraint. + ## 1.0.0 - Initial open-source release. diff --git a/packages/battery/battery_platform_interface/pubspec.yaml b/packages/battery/battery_platform_interface/pubspec.yaml index 6c571debc7b0..e88ef378be6e 100644 --- a/packages/battery/battery_platform_interface/pubspec.yaml +++ b/packages/battery/battery_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the battery plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/battery # 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.0 +version: 1.0.1 dependencies: flutter: @@ -19,4 +19,4 @@ dev_dependencies: environment: sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + flutter: ">=1.9.1+hotfix.4" diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 0a6c050844fa..9789e9e80b69 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,29 @@ +## 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. diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index 39d3ed88abea..f7163818aae3 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -1,6 +1,6 @@ # Camera Plugin -[![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dartlang.org/packages/camera) +[![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera) A Flutter plugin for iOS and Android allowing access to the device cameras. @@ -15,10 +15,12 @@ A Flutter plugin for iOS and Android allowing access to the device cameras. ## Installation -First, add `camera` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). +First, add `camera` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ### iOS +iOS 10.0 of higher is needed to use the camera plugin. If compiling for any version lower than 10.0 make sure to check the iOS version before using the camera plugin. For example, using the [device_info](https://pub.dev/packages/device_info) plugin. + Add two rows to the `ios/Runner/Info.plist`: * one with the key `Privacy - Camera Usage Description` and a usage description. diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 9cf111b9ee69..27f1355319c1 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -17,6 +17,8 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -34,6 +36,8 @@ import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.media.MediaRecorderBuilder; +import io.flutter.plugins.camera.types.FlashMode; +import io.flutter.plugins.camera.types.ResolutionPreset; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; import java.io.File; import java.io.FileOutputStream; @@ -65,18 +69,12 @@ public class Camera { private CaptureRequest.Builder captureRequestBuilder; private MediaRecorder mediaRecorder; private boolean recordingVideo; + private File videoRecordingFile; private CamcorderProfile recordingProfile; private int currentOrientation = ORIENTATION_UNKNOWN; - - // Mirrors camera.dart - public enum ResolutionPreset { - low, - medium, - high, - veryHigh, - ultraHigh, - max, - } + private Context applicationContext; + private FlashMode flashMode; + private PictureCaptureRequest pictureCaptureRequest; public Camera( final Activity activity, @@ -94,6 +92,8 @@ public Camera( this.flutterTexture = flutterTexture; this.dartMessenger = dartMessenger; this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + this.flashMode = FlashMode.auto; orientationEventListener = new OrientationEventListener(activity.getApplicationContext()) { @Override @@ -135,7 +135,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { } @SuppressLint("MissingPermission") - public void open(@NonNull final Result result) throws CameraAccessException { + public void open() throws CameraAccessException { pictureImageReader = ImageReader.newInstance( captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); @@ -154,15 +154,13 @@ public void onOpened(@NonNull CameraDevice device) { try { startPreview(); } catch (CameraAccessException e) { - result.error("CameraAccess", e.getMessage(), null); + dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); return; } - Map reply = new HashMap<>(); - reply.put("textureId", flutterTexture.id()); - reply.put("previewWidth", previewSize.getWidth()); - reply.put("previewHeight", previewSize.getHeight()); - result.success(reply); + + dartMessenger.sendCameraInitializedEvent( + previewSize.getWidth(), previewSize.getHeight()); } @Override @@ -174,7 +172,7 @@ public void onClosed(@NonNull CameraDevice camera) { @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { close(); - dartMessenger.send(DartMessenger.EventType.ERROR, "The camera was disconnected."); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); } @Override @@ -200,7 +198,7 @@ public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { default: errorDescription = "Unknown camera error"; } - dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription); + dartMessenger.sendCameraErrorEvent(errorDescription); } }, null); @@ -218,58 +216,126 @@ SurfaceTextureEntry getFlutterTexture() { return flutterTexture; } - public void takePicture(String filePath, @NonNull final Result result) { - final File file = new File(filePath); + public void takePicture(@NonNull final Result result) { + // Only take 1 picture at a time + if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) { + result.error("captureAlreadyActive", "Picture is currently already being captured", null); + return; + } + // Store the result + this.pictureCaptureRequest = new PictureCaptureRequest(result); - if (file.exists()) { - result.error( - "fileExists", "File at path '" + filePath + "' already exists. Cannot overwrite.", null); + // Create temporary file + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException | SecurityException e) { + pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null); return; } + // Listen for picture being taken pictureImageReader.setOnImageAvailableListener( reader -> { try (Image image = reader.acquireLatestImage()) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); writeToFile(buffer, file); - result.success(null); + pictureCaptureRequest.finish(file.getAbsolutePath()); } catch (IOException e) { - result.error("IOError", "Failed saving image", null); + pictureCaptureRequest.error("IOError", "Failed saving image", null); } }, null); + runPicturePreCapture(); + } + + private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + assert (pictureCaptureRequest != null); + switch (pictureCaptureRequest.getState()) { + case awaitingPreCapture: + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + // Some devices might return null here, in which case we will also continue. + if (aeState == null + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + runPictureCapture(); + } + break; + } + } + + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + assert (pictureCaptureRequest != null); + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + pictureCaptureRequest.error("captureFailure", reason, null); + } + }; + + private void runPicturePreCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture); + + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void runPictureCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing); try { final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(pictureImageReader.getSurface()); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; - default: - reason = "Unknown reason"; - } - result.error("captureFailure", reason, null); - } - }, - null); + switch (flashMode) { + case off: + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case auto: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case always: + default: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + break; + } + cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null); } catch (CameraAccessException e) { - result.error("cameraAccess", e.getMessage(), null); + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); } } @@ -308,26 +374,23 @@ private void createCaptureSession( public void onConfigured(@NonNull CameraCaptureSession session) { try { if (cameraDevice == null) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "The camera was closed during configuration."); + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); return; } cameraCaptureSession = session; - captureRequestBuilder.set( - CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + initPreviewCaptureBuilder(); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); if (onSuccessCallback != null) { onSuccessCallback.run(); } } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.send(DartMessenger.EventType.ERROR, e.getMessage()); + dartMessenger.sendCameraErrorEvent(e.getMessage()); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.send( - DartMessenger.EventType.ERROR, "Failed to configure camera session."); + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); } }; @@ -369,18 +432,24 @@ private void createCaptureSession( cameraDevice.createCaptureSession(surfaces, callback, null); } - public void startVideoRecording(String filePath, Result result) { - if (new File(filePath).exists()) { - result.error("fileExists", "File at path '" + filePath + "' already exists.", null); + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + try { + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException | SecurityException e) { + result.error("cannotCreateFile", e.getMessage(), null); return; } + try { - prepareMediaRecorder(filePath); + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); recordingVideo = true; createCaptureSession( CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); result.success(null); } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; result.error("videoRecordingFailed", e.getMessage(), null); } } @@ -396,7 +465,8 @@ public void stopVideoRecording(@NonNull final Result result) { mediaRecorder.stop(); mediaRecorder.reset(); startPreview(); - result.success(null); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; } catch (CameraAccessException | IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); } @@ -445,6 +515,54 @@ public void resumeVideoRecording(@NonNull final Result result) { result.success(null); } + public void setFlashMode(@NonNull final Result result, FlashMode mode) + throws CameraAccessException { + // Get the flash availability + Boolean flashAvailable; + try { + flashAvailable = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + } catch (CameraAccessException e) { + result.error("setFlashModeFailed", e.getMessage(), null); + return; + } + // Check if flash is available. + if (flashAvailable == null || !flashAvailable) { + result.error("setFlashModeFailed", "Device does not have flash capabilities", null); + return; + } + // Get flash + + this.flashMode = mode; + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + result.success(null); + } + + private void initPreviewCaptureBuilder() { + captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + switch (flashMode) { + case off: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case auto: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case always: + default: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + } + } + public void startPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index a7bb3b7d4914..3b665d6b24f2 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -10,7 +10,7 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.util.Size; -import io.flutter.plugins.camera.Camera.ResolutionPreset; +import io.flutter.plugins.camera.types.ResolutionPreset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index fe385bef7818..49f9d9a76de0 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -3,49 +3,56 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.Map; class DartMessenger { - @Nullable private EventChannel.EventSink eventSink; + @Nullable private MethodChannel channel; enum EventType { ERROR, CAMERA_CLOSING, + INITIALIZED, } - DartMessenger(BinaryMessenger messenger, long eventChannelId) { - new EventChannel(messenger, "flutter.io/cameraPlugin/cameraEvents" + eventChannelId) - .setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object arguments, EventChannel.EventSink sink) { - eventSink = sink; - } - - @Override - public void onCancel(Object arguments) { - eventSink = null; - } - }); + DartMessenger(BinaryMessenger messenger, long cameraId) { + channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); + } + + void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + this.send( + EventType.INITIALIZED, + new HashMap() { + { + if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); + if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); + } + }); } void sendCameraClosingEvent() { - send(EventType.CAMERA_CLOSING, null); + send(EventType.CAMERA_CLOSING); } - void send(EventType eventType, @Nullable String description) { - if (eventSink == null) { - return; - } + void sendCameraErrorEvent(@Nullable String description) { + this.send( + EventType.ERROR, + new HashMap() { + { + if (!TextUtils.isEmpty(description)) put("description", description); + } + }); + } + + void send(EventType eventType) { + send(eventType, new HashMap<>()); + } - Map event = new HashMap<>(); - event.put("eventType", eventType.toString().toLowerCase()); - // Only errors have a description. - if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { - event.put("errorDescription", description); + void send(EventType eventType, Map args) { + if (channel == null) { + return; } - eventSink.success(event); + channel.invokeMethod(eventType.toString().toLowerCase(), args); } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 132075555f26..12b99b72f642 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -10,7 +10,10 @@ 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.types.FlashMode; import io.flutter.view.TextureRegistry; +import java.util.HashMap; +import java.util.Map; final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final Activity activity; @@ -49,11 +52,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) handleException(e, result); } break; - case "initialize": + case "create": { if (camera != null) { camera.close(); } + cameraPermissions.requestPermissions( activity, permissionsRegistry, @@ -69,12 +73,28 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.error(errCode, errDesc, null); } }); - + break; + } + case "initialize": + { + if (camera != null) { + try { + camera.open(); + 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(call.argument("path"), result); + camera.takePicture(result); break; } case "prepareForVideoRecording": @@ -85,7 +105,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } case "startVideoRecording": { - camera.startVideoRecording(call.argument("filePath"), result); + camera.startVideoRecording(result); break; } case "stopVideoRecording": @@ -103,6 +123,21 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) 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 "startImageStream": { try { @@ -157,7 +192,9 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce resolutionPreset, enableAudio); - camera.open(result); + Map reply = new HashMap<>(); + reply.put("cameraId", flutterSurfaceTexture.id()); + result.success(reply); } // We move catching CameraAccessException out of onMethodCall because it causes a crash diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java new file mode 100644 index 000000000000..e365f071d9a8 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -0,0 +1,49 @@ +package io.flutter.plugins.camera; + +import androidx.annotation.Nullable; +import io.flutter.plugin.common.MethodChannel; + +class PictureCaptureRequest { + + enum State { + idle, + awaitingPreCapture, + capturing, + finished, + error, + } + + private final MethodChannel.Result result; + private State state; + + public PictureCaptureRequest(MethodChannel.Result result) { + this.result = result; + state = State.idle; + } + + public void setState(State state) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + this.state = state; + } + + public State getState() { + return state; + } + + public boolean isFinished() { + return state == State.finished || state == State.error; + } + + public void finish(String absolutePath) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + result.success(absolutePath); + state = State.finished; + } + + public void error( + String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + if (isFinished()) throw new IllegalStateException("Request has already been finished"); + result.error(errorCode, errorMessage, errorDetails); + state = State.error; + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java new file mode 100644 index 000000000000..eddeddc47eab --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -0,0 +1,16 @@ +package io.flutter.plugins.camera.types; + +// Mirrors flash_mode.dart +public enum FlashMode { + off, + auto, + always; + + public static FlashMode getValueForString(String modeStr) { + try { + return valueOf(modeStr); + } catch (IllegalArgumentException | NullPointerException e) { + return null; + } + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java new file mode 100644 index 000000000000..ffbe2e62095d --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.camera.types; + +// Mirrors camera.dart +public enum ResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 5a5358229c15..a689f2b6128f 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -2,42 +2,34 @@ import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.junit.Before; import org.junit.Test; public class DartMessengerTest { /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ private static class FakeBinaryMessenger implements BinaryMessenger { - private BinaryMessageHandler handler; private final List sentMessages = new ArrayList<>(); @Override - public void send(String channel, ByteBuffer message) { + public void send(@NonNull String channel, ByteBuffer message) { sentMessages.add(message); } @Override - public void send(String channel, ByteBuffer message, BinaryReply callback) { + public void send(@NonNull String channel, ByteBuffer message, BinaryReply callback) { send(channel, message); } @Override - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - this.handler = handler; - } - - BinaryMessageHandler getMessageHandler() { - return handler; - } + public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) {} List getMessages() { return new ArrayList<>(sentMessages); @@ -54,55 +46,42 @@ public void setUp() { } @Test - public void setsStreamHandler() { - assertNotNull(fakeBinaryMessenger.getMessageHandler()); - } - - @Test - public void send_handlesNullEventSinks() { - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + public void sendCameraErrorEvent_includesErrorDescriptions() { + dartMessenger.sendCameraErrorEvent("error description"); List sentMessages = fakeBinaryMessenger.getMessages(); - assertEquals(0, sentMessages.size()); + assertEquals(1, sentMessages.size()); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals("error", call.method); + assertEquals("error description", call.argument("description")); } @Test - public void send_includesErrorDescriptions() { - initializeEventSink(); - - dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + public void sendCameraInitializedEvent_includesPreviewSize() { + dartMessenger.sendCameraInitializedEvent(0, 0); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), event.get("eventType")); - assertEquals("error description", event.get("errorDescription")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals("initialized", call.method); + assertEquals(0, (double) call.argument("previewWidth"), 0); + assertEquals(0, (double) call.argument("previewHeight"), 0); } @Test public void sendCameraClosingEvent() { - initializeEventSink(); - dartMessenger.sendCameraClosingEvent(); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); - Map event = decodeSentMessage(sentMessages.get(0)); - assertEquals( - DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), event.get("eventType")); - assertNull(event.get("errorDescription")); + MethodCall call = decodeSentMessage(sentMessages.get(0)); + assertEquals("camera_closing", call.method); + assertNull(call.argument("description")); } - @SuppressWarnings("unchecked") - private Map decodeSentMessage(ByteBuffer sentMessage) { + private MethodCall decodeSentMessage(ByteBuffer sentMessage) { sentMessage.position(0); - return (Map) StandardMethodCodec.INSTANCE.decodeEnvelope(sentMessage); - } - private void initializeEventSink() { - MethodCall call = new MethodCall("listen", null); - ByteBuffer encodedCall = StandardMethodCodec.INSTANCE.encodeMethodCall(call); - encodedCall.position(0); - fakeBinaryMessenger.getMessageHandler().onMessage(encodedCall, reply -> {}); + return StandardMethodCodec.INSTANCE.decodeMethodCall(sentMessage); } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java new file mode 100644 index 000000000000..2b6aa0f25fcf --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -0,0 +1,93 @@ +package io.flutter.plugins.camera; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.flutter.plugin.common.MethodChannel; +import org.junit.Test; + +public class PictureCaptureRequestTest { + + @Test + public void state_is_idle_by_default() { + PictureCaptureRequest req = new PictureCaptureRequest(null); + assertEquals("Default state is idle", req.getState(), PictureCaptureRequest.State.idle); + } + + @Test + public void setState_sets_state() { + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.awaitingPreCapture); + assertEquals( + "State is awaitingPreCapture", + req.getState(), + PictureCaptureRequest.State.awaitingPreCapture); + req.setState(PictureCaptureRequest.State.capturing); + assertEquals( + "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing); + } + + @Test + public void finish_sets_result_and_state() { + // Setup + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult); + // Act + req.finish("/test/path"); + // Test + verify(mockResult).success("/test/path"); + assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished); + } + + @Test + public void isFinished_is_true_When_state_is_finished_or_error() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + // Test false states + req.setState(PictureCaptureRequest.State.idle); + assertFalse(req.isFinished()); + req.setState(PictureCaptureRequest.State.awaitingPreCapture); + assertFalse(req.isFinished()); + req.setState(PictureCaptureRequest.State.capturing); + assertFalse(req.isFinished()); + // Test true states + req.setState(PictureCaptureRequest.State.finished); + assertTrue(req.isFinished()); + req = new PictureCaptureRequest(null); // Refresh + req.setState(PictureCaptureRequest.State.error); + assertTrue(req.isFinished()); + } + + @Test(expected = IllegalStateException.class) + public void finish_throws_When_already_finished() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.finished); + // Act + req.finish("/test/path"); + } + + @Test + public void error_sets_result_and_state() { + // Setup + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + PictureCaptureRequest req = new PictureCaptureRequest(mockResult); + // Act + req.error("ERROR_CODE", "Error Message", null); + // Test + verify(mockResult).error("ERROR_CODE", "Error Message", null); + assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error); + } + + @Test(expected = IllegalStateException.class) + public void error_throws_When_already_finished() { + // Setup + PictureCaptureRequest req = new PictureCaptureRequest(null); + req.setState(PictureCaptureRequest.State.finished); + // Act + req.error(null, null, null); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java new file mode 100644 index 000000000000..0549e4fc750e --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java @@ -0,0 +1,26 @@ +package io.flutter.plugins.camera.types; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class FlashModeTest { + + @Test + public void getValueForString_returns_correct_values() { + 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); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null); + } +} diff --git a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml index f37f82306024..f216a7251bcf 100644 --- a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml +++ b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml @@ -37,4 +37,5 @@ android:required="true"/> + diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index ef4646f5ced9..c2e73e0f1563 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -2,9 +2,9 @@ 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:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; import 'package:integration_test/integration_test.dart'; @@ -55,12 +55,10 @@ void main() { 'Capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Picture - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg'; - await controller.takePicture(filePath); + final file = await controller.takePicture(); // Load picture - final File fileImage = File(filePath); + final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected @@ -102,14 +100,12 @@ void main() { 'Capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}'); // Take Video - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); // Load video metadata - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); @@ -160,13 +156,10 @@ void main() { await controller.initialize(); await controller.prepareForVideoRecording(); - final String filePath = - '${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.mp4'; - int startPause; int timePaused = 0; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); @@ -186,11 +179,11 @@ void main() { sleep(const Duration(milliseconds: 500)); - await controller.stopVideoRecording(); + final file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; - final File videoFile = File(filePath); + final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj index 862ee64fb666..d51240a02c14 100644 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,7 +34,6 @@ 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 = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 483D985F075B951ADBAD218E /* 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 = ""; }; 620DDA07C00B5FF2F937CB5B /* 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 = ""; }; @@ -48,7 +41,6 @@ 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 = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; 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 = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -181,6 +169,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 7624MWN53C; }; }; }; @@ -229,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3E30118C54AB12C3EB9EDF27 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -269,9 +258,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -315,7 +307,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -372,7 +363,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -426,6 +416,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -447,6 +438,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = 7624MWN53C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 3ec6604ad788..847779dade09 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; class CameraExampleHome extends StatefulWidget { @@ -38,8 +37,8 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { CameraController controller; - String imagePath; - String videoPath; + XFile imageFile; + XFile videoFile; VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; @@ -102,6 +101,7 @@ class _CameraExampleHomeState extends State ), ), _captureControlRowWidget(), + _flashModeRowWidget(), _toggleAudioWidget(), Padding( padding: const EdgeInsets.all(5.0), @@ -166,11 +166,11 @@ class _CameraExampleHomeState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - videoController == null && imagePath == null + videoController == null && imageFile == null ? Container() : SizedBox( child: (videoController == null) - ? Image.file(File(imagePath)) + ? Image.file(File(imageFile.path)) : Container( child: Center( child: AspectRatio( @@ -192,6 +192,43 @@ class _CameraExampleHomeState extends State ); } + /// Display a bar with buttons to change the flash mode + Widget _flashModeRowWidget() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + IconButton( + icon: const Icon(Icons.flash_off), + color: controller?.value?.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.off) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_auto), + color: controller?.value?.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.auto) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_on), + color: controller?.value?.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onFlashModeButtonPressed(FlashMode.always) + : null, + ), + ], + ); + } + /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { return Row( @@ -306,29 +343,39 @@ class _CameraExampleHomeState extends State } void onTakePictureButtonPressed() { - takePicture().then((String filePath) { + takePicture().then((XFile file) { if (mounted) { setState(() { - imagePath = filePath; + imageFile = file; videoController?.dispose(); videoController = null; }); - if (filePath != null) showInSnackBar('Picture saved to $filePath'); + if (file != null) showInSnackBar('Picture saved to ${file.path}'); } }); } + void onFlashModeButtonPressed(FlashMode mode) { + setFlashMode(mode).then((_) { + if (mounted) setState(() {}); + showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); + }); + } + void onVideoRecordButtonPressed() { - startVideoRecording().then((String filePath) { + startVideoRecording().then((_) { if (mounted) setState(() {}); - if (filePath != null) showInSnackBar('Saving video to $filePath'); }); } void onStopButtonPressed() { - stopVideoRecording().then((_) { + stopVideoRecording().then((file) { if (mounted) setState(() {}); - showInSnackBar('Video recorded to: $videoPath'); + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } }); } @@ -346,45 +393,36 @@ class _CameraExampleHomeState extends State }); } - Future startVideoRecording() async { + Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); - return null; + return; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Movies/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.mp4'; - if (controller.value.isRecordingVideo) { // A recording is already started, do nothing. - return null; + return; } try { - videoPath = filePath; - await controller.startVideoRecording(filePath); + await controller.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); - return null; + return; } - return filePath; } - Future stopVideoRecording() async { + Future stopVideoRecording() async { if (!controller.value.isRecordingVideo) { return null; } try { - await controller.stopVideoRecording(); + return controller.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } - - await _startVideoPlayer(); } Future pauseVideoRecording() async { @@ -413,9 +451,18 @@ class _CameraExampleHomeState extends State } } + Future setFlashMode(FlashMode mode) async { + try { + await controller.setFlashMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + Future _startVideoPlayer() async { - final VideoPlayerController vcontroller = - VideoPlayerController.file(File(videoPath)); + final VideoPlayerController vController = + VideoPlayerController.file(File(videoFile.path)); videoPlayerListener = () { if (videoController != null && videoController.value.size != null) { // Refreshing the state to update video player with the correct ratio. @@ -423,28 +470,24 @@ class _CameraExampleHomeState extends State videoController.removeListener(videoPlayerListener); } }; - vcontroller.addListener(videoPlayerListener); - await vcontroller.setLooping(true); - await vcontroller.initialize(); + vController.addListener(videoPlayerListener); + await vController.setLooping(true); + await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { - imagePath = null; - videoController = vcontroller; + imageFile = null; + videoController = vController; }); } - await vcontroller.play(); + await vController.play(); } - Future takePicture() async { + Future takePicture() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } - final Directory extDir = await getApplicationDocumentsDirectory(); - final String dirPath = '${extDir.path}/Pictures/flutter_test'; - await Directory(dirPath).create(recursive: true); - final String filePath = '$dirPath/${timestamp()}.jpg'; if (controller.value.isTakingPicture) { // A capture is already pending, do nothing. @@ -452,12 +495,12 @@ class _CameraExampleHomeState extends State } try { - await controller.takePicture(filePath); + XFile file = await controller.takePicture(); + return file; } on CameraException catch (e) { _showCameraException(e); return null; } - return filePath; } void _showCameraException(CameraException e) { diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 525c1286717a..cc70ddf209ce 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -7,6 +7,7 @@ #import #import #import +#import static FlutterError *getFlutterError(NSError *error) { return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] @@ -51,10 +52,10 @@ @implementation FLTSavePhotoDelegate { self = [super init]; NSAssert(self, @"super init cannot be nil"); _path = path; - _result = result; _motionManager = motionManager; _cameraPosition = cameraPosition; selfReference = self; + _result = result; return self; } @@ -81,7 +82,7 @@ - (void)captureOutput:(AVCapturePhotoOutput *)output _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]); return; } - _result(nil); + _result(_path); } - (UIImageOrientation)getImageRotation { @@ -113,6 +114,24 @@ - (UIImageOrientation)getImageRotation { } @end +static AVCaptureFlashMode getFlashModeForString(NSString *mode) { + if ([mode isEqualToString:@"off"]) { + return AVCaptureFlashModeOff; + } else if ([mode isEqualToString:@"auto"]) { + return AVCaptureFlashModeAuto; + } else if ([mode isEqualToString:@"always"]) { + return AVCaptureFlashModeOn; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown flash mode %@", mode] + }]; + @throw error; + } +} + // Mirrors ResolutionPreset in camera.dart typedef enum { veryLow, @@ -152,14 +171,12 @@ static ResolutionPreset getResolutionPresetForString(NSString *preset) { @interface FLTCam : NSObject + AVCaptureAudioDataOutputSampleBufferDelegate> @property(readonly, nonatomic) int64_t textureId; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property BOOL enableAudio; -@property(nonatomic) FlutterEventChannel *eventChannel; @property(nonatomic) FLTImageStreamHandler *imageStreamHandler; -@property(nonatomic) FlutterEventSink eventSink; +@property(nonatomic) FlutterMethodChannel *methodChannel; @property(readonly, nonatomic) AVCaptureSession *captureSession; @property(readonly, nonatomic) AVCaptureDevice *captureDevice; @property(readonly, nonatomic) AVCapturePhotoOutput *capturePhotoOutput API_AVAILABLE(ios(10)); @@ -174,6 +191,7 @@ @interface FLTCam : NSObject _videoWriter.status == AVAssetWriterStatusCompleted) { - result(nil); + result(self->_videoRecordingPath); + self->_videoRecordingPath = nil; } else { - self->_eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"AVAssetWriter could not finish writing!" - }); + result([FlutterError errorWithCode:@"IOError" + message:@"AVAssetWriter could not finish writing!" + details:nil]); } }]; } @@ -620,14 +661,41 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result { } } -- (void)pauseVideoRecording { +- (void)pauseVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = YES; _videoIsDisconnected = YES; _audioIsDisconnected = YES; + result(nil); } -- (void)resumeVideoRecording { +- (void)resumeVideoRecordingWithResult:(FlutterResult)result { _isRecordingPaused = NO; + result(nil); +} + +- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr { + AVCaptureFlashMode mode; + @try { + mode = getFlashModeForString(modeStr); + } @catch (NSError *e) { + result(getFlutterError(e)); + return; + } + if (!_captureDevice.hasFlash) { + result([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not have flash capabilities" + details:nil]); + return; + } + if (![_capturePhotoOutput.supportedFlashModes + containsObject:[NSNumber numberWithInt:((int)mode)]]) { + result([FlutterError errorWithCode:@"setFlashModeFailed" + message:@"Device does not support this specific flash mode" + details:nil]); + return; + } + _flashMode = mode; + result(nil); } - (void)startImageStreamWithMessenger:(NSObject *)messenger { @@ -641,8 +709,8 @@ - (void)startImageStreamWithMessenger:(NSObject *)messen _isStreamingImages = YES; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are already streaming!"}); + [_methodChannel invokeMethod:errorMethod + arguments:@"Images from camera are already streaming!"]; } } @@ -651,8 +719,7 @@ - (void)stopImageStream { _isStreamingImages = NO; _imageStreamHandler = nil; } else { - _eventSink( - @{@"event" : @"error", @"errorDescription" : @"Images from camera are not streaming!"}); + [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are not streaming!"]; } } @@ -672,7 +739,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { error:&error]; NSParameterAssert(_videoWriter); if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:errorMethod arguments:error.description]; return NO; } NSDictionary *videoSettings = [NSDictionary @@ -726,7 +793,7 @@ - (void)setUpCaptureSessionForAudio { AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (error) { - _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); + [_methodChannel invokeMethod:errorMethod arguments:error.description]; } // Setup the audio output. _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; @@ -738,10 +805,8 @@ - (void)setUpCaptureSessionForAudio { [_captureSession addOutput:_audioOutput]; _isAudioSetup = YES; } else { - _eventSink(@{ - @"event" : @"error", - @"errorDescription" : @"Unable to add Audio input/output to session capture" - }); + [_methodChannel invokeMethod:errorMethod + arguments:@"Unable to add Audio input/output to session capture"]; _isAudioSetup = NO; } } @@ -819,7 +884,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else { result(FlutterMethodNotImplemented); } - } else if ([@"initialize" isEqualToString:call.method]) { + } else if ([@"create" isEqualToString:call.method]) { NSString *cameraName = call.arguments[@"cameraName"]; NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; NSNumber *enableAudio = call.arguments[@"enableAudio"]; @@ -829,6 +894,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re enableAudio:[enableAudio boolValue] dispatchQueue:_dispatchQueue error:&error]; + if (error) { result(getFlutterError(error)); } else { @@ -837,25 +903,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } int64_t textureId = [_registry registerTexture:cam]; _camera = cam; - __weak CameraPlugin *weakSelf = self; - cam.onFrameAvailable = ^{ - [weakSelf.registry textureFrameAvailable:textureId]; - }; - FlutterEventChannel *eventChannel = [FlutterEventChannel - eventChannelWithName:[NSString - stringWithFormat:@"flutter.io/cameraPlugin/cameraEvents%lld", - textureId] - binaryMessenger:_messenger]; - [eventChannel setStreamHandler:cam]; - cam.eventChannel = eventChannel; + result(@{ - @"textureId" : @(textureId), - @"previewWidth" : @(cam.previewSize.width), - @"previewHeight" : @(cam.previewSize.height), - @"captureWidth" : @(cam.captureSize.width), - @"captureHeight" : @(cam.captureSize.height), + @"cameraId" : @(textureId), }); - [cam start]; } } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; @@ -863,23 +914,34 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else if ([@"stopImageStream" isEqualToString:call.method]) { [_camera stopImageStream]; result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { - [_camera pauseVideoRecording]; - result(nil); - } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { - [_camera resumeVideoRecording]; - result(nil); } else { NSDictionary *argsMap = call.arguments; - NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; - if ([@"takePicture" isEqualToString:call.method]) { + NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; + if ([@"initialize" isEqualToString:call.method]) { + __weak CameraPlugin *weakSelf = self; + _camera.onFrameAvailable = ^{ + [weakSelf.registry textureFrameAvailable:cameraId]; + }; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel + methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", + (unsigned long)cameraId] + binaryMessenger:_messenger]; + _camera.methodChannel = methodChannel; + [methodChannel invokeMethod:@"initialized" + arguments:@{ + @"previewWidth" : @(_camera.previewSize.width), + @"previewHeight" : @(_camera.previewSize.height) + }]; + [_camera start]; + result(nil); + } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { - [_camera captureToFile:call.arguments[@"path"] result:result]; + [_camera captureToFile:result]; } else { result(FlutterMethodNotImplemented); } } else if ([@"dispose" isEqualToString:call.method]) { - [_registry unregisterTexture:textureId]; + [_registry unregisterTexture:cameraId]; [_camera close]; _dispatchQueue = nil; result(nil); @@ -887,9 +949,15 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setUpCaptureSessionForAudio]; result(nil); } else if ([@"startVideoRecording" isEqualToString:call.method]) { - [_camera startVideoRecordingAtPath:call.arguments[@"filePath"] result:result]; + [_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 ([@"setFlashMode" isEqualToString:call.method]) { + [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 3b2cd77c5757..6c6214e96951 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -2,643 +2,15 @@ // 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/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -part 'camera_image.dart'; - -final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); - -/// 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, -} - -/// 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 - low, - - /// 480p (640x480 on iOS, 720x480 on Android) - medium, - - /// 720p (1280x720) - high, - - /// 1080p (1920x1080) - veryHigh, - - /// 2160p (3840x2160) - ultraHigh, - - /// The highest resolution available. - max, -} - -/// Signature for a callback receiving the a camera image. -/// -/// This is used by [CameraController.startImageStream]. -// ignore: inference_failure_on_function_return_type -typedef onLatestImageAvailable = Function(CameraImage image); - -/// 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'; - } - throw ArgumentError('Unknown ResolutionPreset value'); -} - -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'); -} - -/// Completes with a list of available cameras. -/// -/// May throw a [CameraException]. -Future> availableCameras() async { - try { - final List> cameras = await _channel - .invokeListMethod>('availableCameras'); - return cameras.map((Map camera) { - return CameraDescription( - name: camera['name'], - lensDirection: _parseCameraLensDirection(camera['lensFacing']), - sensorOrientation: camera['sensorOrientation'], - ); - }).toList(); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } -} - -/// Properties of a camera device. -class CameraDescription { - /// Creates a new camera description with the given properties. - CameraDescription({this.name, this.lensDirection, 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 o) { - return o is CameraDescription && - o.name == name && - o.lensDirection == lensDirection; - } - - @override - int get hashCode { - return hashValues(name, lensDirection); - } - - @override - String toString() { - return '$runtimeType($name, $lensDirection, $sensorOrientation)'; - } -} - -/// 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() => '$runtimeType($code, $description)'; -} - -/// A widget showing a live camera preview. -class CameraPreview extends StatelessWidget { - /// Creates a preview widget for the given camera controller. - const CameraPreview(this.controller); - - /// The controller for the camera that the preview is shown for. - final CameraController controller; - - @override - Widget build(BuildContext context) { - return controller.value.isInitialized - ? Texture(textureId: controller._textureId) - : Container(); - } -} - -/// The state of a [CameraController]. -class CameraValue { - /// Creates a new camera controller state. - const CameraValue({ - this.isInitialized, - this.errorDescription, - this.previewSize, - this.isRecordingVideo, - this.isTakingPicture, - this.isStreamingImages, - bool isRecordingPaused, - }) : _isRecordingPaused = isRecordingPaused; - - /// Creates a new camera controller state for an uninitialzed controller. - const CameraValue.uninitialized() - : this( - isInitialized: false, - isRecordingVideo: false, - isTakingPicture: false, - isStreamingImages: false, - isRecordingPaused: 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 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.height / previewSize.width`. - /// - /// Can only be called when [initialize] is done. - double get aspectRatio => previewSize.height / previewSize.width; - - /// Whether the controller is in an error state. - /// - /// When true [errorDescription] describes the error. - bool get hasError => errorDescription != null; - - /// 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, - }) { - 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, - ); - } - - @override - String toString() { - return '$runtimeType(' - 'isRecordingVideo: $isRecordingVideo, ' - 'isRecordingVideo: $isRecordingVideo, ' - 'isInitialized: $isInitialized, ' - 'errorDescription: $errorDescription, ' - 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages)'; - } -} - -/// 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, - }) : 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; - - int _textureId; - bool _isDisposed = false; - StreamSubscription _eventSubscription; - StreamSubscription _imageStreamSubscription; - Completer _creatingCompleter; - - /// Checks whether [CameraController.dispose] has completed successfully. - /// - /// This is a no-op when asserts are disabled. - void debugCheckIsDisposed() { - assert(_isDisposed); - } - - /// Initializes the camera on the device. - /// - /// Throws a [CameraException] if the initialization fails. - Future initialize() async { - if (_isDisposed) { - return Future.value(); - } - try { - _creatingCompleter = Completer(); - final Map reply = - await _channel.invokeMapMethod( - 'initialize', - { - 'cameraName': description.name, - 'resolutionPreset': serializeResolutionPreset(resolutionPreset), - 'enableAudio': enableAudio, - }, - ); - _textureId = reply['textureId']; - value = value.copyWith( - isInitialized: true, - previewSize: Size( - reply['previewWidth'].toDouble(), - reply['previewHeight'].toDouble(), - ), - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - _eventSubscription = - EventChannel('flutter.io/cameraPlugin/cameraEvents$_textureId') - .receiveBroadcastStream() - .listen(_listener); - _creatingCompleter.complete(); - return _creatingCompleter.future; - } - - /// 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. - /// - /// Throws a [CameraException] if the prepare fails. - Future prepareForVideoRecording() async { - await _channel.invokeMethod('prepareForVideoRecording'); - } - - /// Listen to events from the native plugins. - /// - /// A "cameraClosing" event is sent when the camera is closed automatically by the system (for example when the app go to background). The plugin will try to reopen the camera automatically but any ongoing recording will end. - void _listener(dynamic event) { - final Map map = event; - if (_isDisposed) { - return; - } - - switch (map['eventType']) { - case 'error': - value = value.copyWith(errorDescription: event['errorDescription']); - break; - case 'cameraClosing': - value = value.copyWith(isRecordingVideo: false); - break; - } - } - - /// Captures an image and saves it to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as this function returns. - /// - /// Throws a [CameraException] if the capture fails. - Future takePicture(String path) async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController.', - 'takePicture was called on uninitialized CameraController', - ); - } - 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); - await _channel.invokeMethod( - 'takePicture', - {'textureId': _textureId, 'path': path}, - ); - value = value.copyWith(isTakingPicture: false); - } 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. - // TODO(bmparr): Add settings for resolution and fps. - Future startImageStream(onLatestImageAvailable onAvailable) async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startImageStream was called on uninitialized CameraController.', - ); - } - 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 { - await _channel.invokeMethod('startImageStream'); - value = value.copyWith(isStreamingImages: true); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( - (dynamic imageData) { - onAvailable(CameraImage._fromPlatformData(imageData)); - }, - ); - } - - /// Stop streaming images from platform camera. - /// - /// Throws a [CameraException] if image streaming was not started or video - /// recording was started. - Future stopImageStream() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopImageStream was called on uninitialized CameraController.', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'stopImageStream was called while a video is being recorded.', - ); - } - if (!value.isStreamingImages) { - throw CameraException( - 'No camera is streaming images', - 'stopImageStream was called when no camera is streaming images.', - ); - } - - try { - value = value.copyWith(isStreamingImages: false); - await _channel.invokeMethod('stopImageStream'); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - - await _imageStreamSubscription.cancel(); - _imageStreamSubscription = null; - } - - /// Start a video recording and save the file to [path]. - /// - /// A path can for example be obtained using - /// [path_provider](https://pub.dartlang.org/packages/path_provider). - /// - /// The file is written on the flight as the video is being recorded. - /// If a file already exists at the provided path an error will be thrown. - /// The file can be read as soon as [stopVideoRecording] returns. - /// - /// Throws a [CameraException] if the capture fails. - Future startVideoRecording(String filePath) async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ); - } - if (value.isRecordingVideo) { - throw CameraException( - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ); - } - if (value.isStreamingImages) { - throw CameraException( - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ); - } - - try { - await _channel.invokeMethod( - 'startVideoRecording', - {'textureId': _textureId, 'filePath': filePath}, - ); - value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Stop recording. - Future stopVideoRecording() async { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'stopVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'stopVideoRecording was called when no video is recording.', - ); - } - try { - value = value.copyWith(isRecordingVideo: false); - await _channel.invokeMethod( - 'stopVideoRecording', - {'textureId': _textureId}, - ); - } 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 { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'pauseVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'pauseVideoRecording was called when no video is recording.', - ); - } - try { - value = value.copyWith(isRecordingPaused: true); - await _channel.invokeMethod( - 'pauseVideoRecording', - {'textureId': _textureId}, - ); - } 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 { - if (!value.isInitialized || _isDisposed) { - throw CameraException( - 'Uninitialized CameraController', - 'resumeVideoRecording was called on uninitialized CameraController', - ); - } - if (!value.isRecordingVideo) { - throw CameraException( - 'No video is recording', - 'resumeVideoRecording was called when no video is recording.', - ); - } - try { - value = value.copyWith(isRecordingPaused: false); - await _channel.invokeMethod( - 'resumeVideoRecording', - {'textureId': _textureId}, - ); - } on PlatformException catch (e) { - throw CameraException(e.code, e.message); - } - } - - /// Releases the resources of this camera. - @override - Future dispose() async { - if (_isDisposed) { - return; - } - _isDisposed = true; - super.dispose(); - if (_creatingCompleter != null) { - await _creatingCompleter.future; - await _channel.invokeMethod( - 'dispose', - {'textureId': _textureId}, - ); - await _eventSubscription?.cancel(); - } - } -} +export 'src/camera_controller.dart'; +export 'src/camera_image.dart'; +export 'src/camera_preview.dart'; + +export 'package:camera_platform_interface/camera_platform_interface.dart' + show + CameraDescription, + CameraException, + CameraLensDirection, + FlashMode, + ResolutionPreset, + XFile; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart new file mode 100644 index 000000000000..2c2ce3b633f1 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -0,0 +1,502 @@ +// Copyright 2018 The Chromium Authors. 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/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraController.startImageStream]. +// ignore: inference_failure_on_function_return_type +typedef onLatestImageAvailable = Function(CameraImage image); + +/// Completes with a list of available cameras. +/// +/// May throw a [CameraException]. +Future> availableCameras() async { + return CameraPlatform.instance.availableCameras(); +} + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + this.isInitialized, + this.errorDescription, + this.previewSize, + this.isRecordingVideo, + this.isTakingPicture, + this.isStreamingImages, + bool isRecordingPaused, + this.flashMode, + }) : _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, + ); + + /// 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 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.height / previewSize.width`. + /// + /// Can only be called when [initialize] is done. + double get aspectRatio => previewSize.height / previewSize.width; + + /// 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; + + /// 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, + }) { + 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, + ); + } + + @override + String toString() { + return '$runtimeType(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'errorDescription: $errorDescription, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages, ' + 'flashMode: $flashMode)'; + } +} + +/// 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, + }) : 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; + + int _cameraId; + bool _isDisposed = false; + StreamSubscription _imageStreamSubscription; + FutureOr _initCalled; + + /// 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 { + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + final previewSize = + CameraPlatform.instance.onCameraInitialized(_cameraId).map((event) { + return Size( + event.previewWidth, + event.previewHeight, + ); + }).first; + + await CameraPlatform.instance.initializeCamera(_cameraId); + + value = value.copyWith( + isInitialized: true, + previewSize: await previewSize, + ); + } 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. + /// + /// Throws a [CameraException] if the prepare fails. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Captures an image and saves it to [path]. + /// + /// A path can for example be obtained using + /// [path_provider](https://pub.dartlang.org/packages/path_provider). + /// + /// If a file already exists at the provided path an error will be thrown. + /// The file can be read as this function returns. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ); + } + 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); + 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); + + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startImageStream was called on uninitialized CameraController.', + ); + } + 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 { + await _channel.invokeMethod('startImageStream'); + value = value.copyWith(isStreamingImages: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + const EventChannel cameraEventChannel = + EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = + cameraEventChannel.receiveBroadcastStream().listen( + (dynamic imageData) { + onAvailable(CameraImage.fromPlatformData(imageData)); + }, + ); + } + + /// 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); + + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopImageStream was called on uninitialized CameraController.', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ); + } + if (!value.isStreamingImages) { + throw CameraException( + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ); + } + + try { + value = value.copyWith(isStreamingImages: false); + await _channel.invokeMethod('stopImageStream'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + + 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() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ); + } + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ); + } + + try { + await CameraPlatform.instance.startVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: true, isRecordingPaused: false); + } 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 { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'stopVideoRecording was called on uninitialized CameraController', + ); + } + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'stopVideoRecording was called when no video is recording.', + ); + } + try { + XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith(isRecordingVideo: false); + 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 { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'pauseVideoRecording was called on uninitialized CameraController', + ); + } + 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 { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'resumeVideoRecording was called on uninitialized CameraController', + ); + } + 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() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'buildView() was called on uninitialized CameraController.', + ); + } + try { + return CameraPlatform.instance.buildPreview(_cameraId); + } 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); + } + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _isDisposed = true; + super.dispose(); + if (_initCalled != null) { + await _initCalled; + await CameraPlatform.instance.dispose(_cameraId); + } + } +} diff --git a/packages/camera/camera/lib/camera_image.dart b/packages/camera/camera/lib/src/camera_image.dart similarity index 96% rename from packages/camera/camera/lib/camera_image.dart rename to packages/camera/camera/lib/src/camera_image.dart index cebc14873f52..ca8115eb758d 100644 --- a/packages/camera/camera/lib/camera_image.dart +++ b/packages/camera/camera/lib/src/camera_image.dart @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of 'camera.dart'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// A single color plane of image data. /// @@ -113,7 +116,8 @@ ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { /// Although not all image formats are planar on iOS, we treat 1-dimensional /// images as single planar images. class CameraImage { - CameraImage._fromPlatformData(Map data) + /// CameraImage Constructor + CameraImage.fromPlatformData(Map data) : format = ImageFormat._fromPlatformData(data['format']), height = data['height'], width = data['width'], diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart new file mode 100644 index 000000000000..bf7862eb9151 --- /dev/null +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -0,0 +1,23 @@ +// Copyright 2018 The Chromium Authors. All 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/material.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); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + @override + Widget build(BuildContext context) { + return controller.value.isInitialized + ? CameraPlatform.instance.buildPreview(controller.cameraId) + : Container(); + } +} diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index af8979d0df03..3194fff33684 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,12 +2,13 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.8+18 +version: 0.6.1 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter + camera_platform_interface: ^1.0.4 dev_dependencies: path_provider: ^0.5.0 @@ -17,6 +18,8 @@ dev_dependencies: flutter_driver: sdk: flutter pedantic: ^1.8.0 + mockito: ^4.1.3 + plugin_platform_interface: ^1.0.3 flutter: plugin: @@ -29,4 +32,4 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/camera/camera/test/camera_image_stream_test.dart b/packages/camera/camera/test/camera_image_stream_test.dart new file mode 100644 index 000000000000..be7047f2220f --- /dev/null +++ b/packages/camera/camera/test/camera_image_stream_test.dart @@ -0,0 +1,187 @@ +import 'package:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'camera_test.dart'; +import 'utils/method_channel_mock.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + CameraPlatform.instance = MockCameraPlatform(); + }); + + test('startImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'startImageStream was called on uninitialized CameraController.', + ))); + }); + + test('startImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (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 { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + expect( + () => cameraController.startImageStream((image) => null), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ))); + }); + + test('startImageStream() calls CameraPlatform', () async { + MethodChannelMock cameraChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}}); + MethodChannelMock streamChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera/imageStream', + methods: {'listen': {}}); + + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + + expect(cameraChannelMock.log, + [isMethodCall('startImageStream', arguments: null)]); + expect(streamChannelMock.log, + [isMethodCall('listen', arguments: null)]); + }); + + test('stopImageStream() throws $CameraException when uninitialized', () { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'stopImageStream was called on uninitialized CameraController.', + ))); + }); + + test('stopImageStream() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.startImageStream((image) => null); + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'stopImageStream was called while a video is being recorded.', + ))); + }); + + test('stopImageStream() throws $CameraException when not streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect( + cameraController.stopImageStream, + throwsA(isA().having( + (error) => error.description, + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ))); + }); + + test('stopImageStream() intended behaviour', () async { + MethodChannelMock cameraChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'startImageStream': {}, 'stopImageStream': {}}); + MethodChannelMock streamChannelMock = MethodChannelMock( + channelName: 'plugins.flutter.io/camera/imageStream', + methods: {'listen': {}, 'cancel': {}}); + + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + await cameraController.startImageStream((image) => null); + await cameraController.stopImageStream(); + + expect(cameraChannelMock.log, [ + isMethodCall('startImageStream', arguments: null), + isMethodCall('stopImageStream', arguments: null) + ]); + + expect(streamChannelMock.log, [ + isMethodCall('listen', arguments: null), + isMethodCall('cancel', arguments: null) + ]); + }); +} diff --git a/packages/camera/camera/test/camera_image_test.dart b/packages/camera/camera/test/camera_image_test.dart new file mode 100644 index 000000000000..c8f808f2c1a1 --- /dev/null +++ b/packages/camera/camera/test/camera_image_test.dart @@ -0,0 +1,113 @@ +// Copyright 2019 The Chromium Authors. 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:camera/camera.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('$CameraImage tests', () { + test('$CameraImage can be created', () { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + '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; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 875704438, + 'height': 1, + 'width': 4, + '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; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 35, + 'height': 1, + 'width': 4, + '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; + + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': 1111970369, + 'height': 1, + 'width': 4, + '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', () { + CameraImage cameraImage = CameraImage.fromPlatformData({ + 'format': null, + 'height': 1, + 'width': 4, + 'planes': [ + { + 'bytes': Uint8List.fromList([1, 2, 3, 4]), + 'bytesPerPixel': 1, + 'bytesPerRow': 4, + 'height': 1, + 'width': 4 + } + ] + }); + expect(cameraImage.format.group, ImageFormatGroup.unknown); + }); + }); +} diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index cc33b369f000..c19aa9718f47 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1,10 +1,46 @@ // Copyright 2017 The Chromium Authors. 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'; + import 'package:camera/camera.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 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +get mockAvailableCameras => [ + CameraDescription( + name: 'camBack', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + CameraDescription( + name: 'camFront', + lensDirection: CameraLensDirection.front, + sensorOrientation: 180), + ]; + +get mockInitializeCamera => 13; + +get mockOnCameraInitializedEvent => CameraInitializedEvent(13, 75, 75); + +get mockOnCameraClosingEvent => null; + +get mockOnCameraErrorEvent => CameraErrorEvent(13, 'closing'); + +XFile mockTakePicture = XFile('foo/bar.png'); + +get mockVideoRecordingXFile => null; + +bool mockPlatformException = false; void main() { + WidgetsFlutterBinding.ensureInitialized(); + group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { @@ -32,9 +68,335 @@ void main() { throwsAssertionError, ); }); + + test('availableCameras() has camera', () async { + CameraPlatform.instance = MockCameraPlatform(); + + var camList = await availableCameras(); + + expect(camList, equals(mockAvailableCameras)); + }); + }); + + group('$CameraController', () { + setUpAll(() { + CameraPlatform.instance = MockCameraPlatform(); + }); + + test('Can be initialized', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + }); + + test('can be disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, 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 { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + expect(cameraController.value.aspectRatio, 1); + expect(cameraController.value.previewSize, Size(75, 75)); + expect(cameraController.value.isInitialized, isTrue); + + await cameraController.dispose(); + + verify(CameraPlatform.instance.dispose(13)).called(1); + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'Error description', + 'initialize was called on a disposed CameraController', + ))); + }); + + test('initialize() throws $CameraException on $PlatformException ', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + mockPlatformException = true; + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('prepareForVideoRecording() calls $CameraPlatform ', () async { + CameraController cameraController = CameraController( + 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 { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController.', + 'takePicture was called on uninitialized CameraController', + ))); + }); + + test('takePicture() throws $CameraException when takePicture is true', + () async { + CameraController cameraController = CameraController( + 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( + (error) => error.description, + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ))); + }); + + test('takePicture() returns $XFile', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + XFile xFile = await cameraController.takePicture(); + + expect(xFile.path, mockTakePicture.path); + }); + + test('takePicture() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); + + test('startVideoRecording() throws $CameraException when uninitialized', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); + }); + test('startVideoRecording() throws $CameraException when recording videos', + () async { + CameraController cameraController = CameraController( + 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( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); + }); + + test( + 'startVideoRecording() throws $CameraException when already streaming images', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); + }); + + test('setFlashMode() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + 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 { + CameraController cameraController = CameraController( + 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', + details: null, + ), + ); + + expect( + cameraController.setFlashMode(FlashMode.always), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); }); } +class MockCameraPlatform extends Mock + with MockPlatformInterfaceMixin + implements CameraPlatform { + @override + Future> availableCameras() => + Future.value(mockAvailableCameras); + + @override + Future createCamera( + CameraDescription description, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) => + 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 + Future takePicture(int cameraId) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); + + @override + Future startVideoRecording(int cameraId) => + Future.value(mockVideoRecordingXFile); +} + class MockCameraDescription extends CameraDescription { @override CameraLensDirection get lensDirection => CameraLensDirection.back; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart new file mode 100644 index 000000000000..06b327cb1c29 --- /dev/null +++ b/packages/camera/camera/test/camera_value_test.dart @@ -0,0 +1,106 @@ +// Copyright 2019 The Chromium Authors. 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:camera/camera.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('camera_value', () { + test('Can be created', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + flashMode: FlashMode.auto, + ); + + expect(cameraValue, isA()); + expect(cameraValue.isInitialized, isFalse); + expect(cameraValue.errorDescription, null); + expect(cameraValue.previewSize, Size(10, 10)); + expect(cameraValue.isRecordingPaused, isFalse); + expect(cameraValue.isRecordingVideo, isFalse); + expect(cameraValue.isTakingPicture, isFalse); + expect(cameraValue.isStreamingImages, isFalse); + }); + + test('Can be created as uninitialized', () { + var cameraValue = const 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); + }); + + test('Can be copied with isInitialized', () { + var cv = const CameraValue.uninitialized(); + var 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); + }); + + test('Has aspectRatio after setting size', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = + cv.copyWith(isInitialized: true, previewSize: Size(20, 10)); + + expect(cameraValue.aspectRatio, 0.5); + }); + + test('hasError is true after setting errorDescription', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith(errorDescription: 'error'); + + expect(cameraValue.hasError, isTrue); + expect(cameraValue.errorDescription, 'error'); + }); + + test('Recording paused is false when not recording', () { + var cv = const CameraValue.uninitialized(); + var cameraValue = cv.copyWith( + isInitialized: true, + isRecordingVideo: false, + isRecordingPaused: true); + + expect(cameraValue.isRecordingPaused, isFalse); + }); + + test('toString() works as expected', () { + var cameraValue = const CameraValue( + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + flashMode: FlashMode.auto, + ); + + expect(cameraValue.toString(), + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto)'); + }); + }); +} diff --git a/packages/camera/camera/test/utils/method_channel_mock.dart b/packages/camera/camera/test/utils/method_channel_mock.dart new file mode 100644 index 000000000000..cdf393f82b5f --- /dev/null +++ b/packages/camera/camera/test/utils/method_channel_mock.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights 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'; + +class MethodChannelMock { + final Duration delay; + final MethodChannel methodChannel; + final Map methods; + final log = []; + + MethodChannelMock({ + String channelName, + this.delay, + this.methods, + }) : methodChannel = MethodChannel(channelName) { + methodChannel.setMockMethodCallHandler(_handler); + } + + 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 result = methods[methodCall.method]; + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +} diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index ac0dce2cb4ea..ea9821e841f9 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index bc836b0ac98e..3bf996fedb19 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -226,6 +226,8 @@ class MethodChannelCamera extends CameraPlatform { return 'auto'; case FlashMode.always: return 'always'; + case FlashMode.torch: + return 'torch'; default: throw ArgumentError('Unknown FlashMode value'); } diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index c398e9e9ef17..6f96079dc55c 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -108,7 +108,7 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('resumeVideoRecording() is not implemented.'); } - /// Sets the flash mode for taking pictures. + /// Sets the flash mode for the selected camera. Future setFlashMode(int cameraId, FlashMode mode) { throw UnimplementedError('setFlashMode() is not implemented.'); } diff --git a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart index 6ed92e4801eb..7feb59caeab8 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart @@ -12,4 +12,7 @@ enum FlashMode { /// Always use the flash when taking a picture. always, + + /// Turns on the flash light and keeps it on until switched off. + torch, } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 1d1a7ec4565b..8cb643e84ca6 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface # 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.2 +version: 1.0.4 dependencies: flutter: @@ -22,4 +22,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" + flutter: ">=1.22.0" diff --git a/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart b/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart index 59726acf7873..c9df64152cef 100644 --- a/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/flash_mode_test.dart @@ -6,10 +6,10 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - test('FlashMode should contain 3 options', () { + test('FlashMode should contain 4 options', () { final values = FlashMode.values; - expect(values.length, 3); + expect(values.length, 4); }); test("FlashMode enum should have items in correct index", () { @@ -18,5 +18,6 @@ void main() { expect(values[0], FlashMode.off); expect(values[1], FlashMode.auto); expect(values[2], FlashMode.always); + expect(values[3], FlashMode.torch); }); } diff --git a/packages/connectivity/connectivity/CHANGELOG.md b/packages/connectivity/connectivity/CHANGELOG.md index 72b6d9b6513a..a1d0231a5bd4 100644 --- a/packages/connectivity/connectivity/CHANGELOG.md +++ b/packages/connectivity/connectivity/CHANGELOG.md @@ -1,3 +1,23 @@ +## 3.0.0-nullsafety.3 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 3.0.0-nullsafety.2 + +* Android: Cleanup the NetworkCallback object when a connectivity stream is cancelled + +## 3.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 3.0.0-nullsafety + +* Migrate to null safety. + +## 2.0.3 + +* Update Flutter SDK constraint. + ## 2.0.2 * Android: Fix IllegalArgumentException. diff --git a/packages/connectivity/connectivity/README.md b/packages/connectivity/connectivity/README.md index a6a1dcc2838a..6c608d3d8e16 100644 --- a/packages/connectivity/connectivity/README.md +++ b/packages/connectivity/connectivity/README.md @@ -55,6 +55,6 @@ Note that connectivity changes are no longer communicated to Android apps in the ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/connectivity/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java b/packages/connectivity/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java index ca3ccff82d2d..dbf96bda9fe8 100644 --- a/packages/connectivity/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java +++ b/packages/connectivity/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java @@ -64,6 +64,7 @@ public void onCancel(Object arguments) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (networkCallback != null) { connectivity.getConnectivityManager().unregisterNetworkCallback(networkCallback); + networkCallback = null; } } else { context.unregisterReceiver(this); diff --git a/packages/connectivity/connectivity/example/lib/main.dart b/packages/connectivity/connectivity/example/lib/main.dart index e05497136b86..19285ce889fd 100644 --- a/packages/connectivity/connectivity/example/lib/main.dart +++ b/packages/connectivity/connectivity/example/lib/main.dart @@ -40,7 +40,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @@ -51,7 +51,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { String _connectionStatus = 'Unknown'; final Connectivity _connectivity = Connectivity(); - StreamSubscription _connectivitySubscription; + late StreamSubscription _connectivitySubscription; @override void initState() { @@ -69,7 +69,7 @@ class _MyHomePageState extends State { // Platform messages are asynchronous, so we initialize in an async method. Future initConnectivity() async { - ConnectivityResult result; + ConnectivityResult result = ConnectivityResult.none; // Platform messages may fail, so we use a try/catch PlatformException. try { result = await _connectivity.checkConnectivity(); diff --git a/packages/connectivity/connectivity/example/pubspec.yaml b/packages/connectivity/connectivity/example/pubspec.yaml index bff35483d763..682f0a2dd08a 100644 --- a/packages/connectivity/connectivity/example/pubspec.yaml +++ b/packages/connectivity/connectivity/example/pubspec.yaml @@ -10,13 +10,14 @@ dependencies: dev_dependencies: flutter_driver: sdk: flutter + test: ^1.10.0-nullsafety.1 integration_test: path: ../../../integration_test - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 flutter: uses-material-design: true environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/connectivity/connectivity/example/test_driver/integration_test/connectivity_test.dart b/packages/connectivity/connectivity/example/test_driver/integration_test/connectivity_test.dart index d48deae3403d..3177b66ea48a 100644 --- a/packages/connectivity/connectivity/example/test_driver/integration_test/connectivity_test.dart +++ b/packages/connectivity/connectivity/example/test_driver/integration_test/connectivity_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(cyanglaz): Remove once https://github.com/flutter/plugins/pull/3158 is landed. +// @dart = 2.9 + import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:connectivity/connectivity.dart'; diff --git a/packages/connectivity/connectivity/example/test_driver/test/integration_test.dart b/packages/connectivity/connectivity/example/test_driver/test/integration_test.dart index c0cbdcab2fb6..33421c335bdd 100644 --- a/packages/connectivity/connectivity/example/test_driver/test/integration_test.dart +++ b/packages/connectivity/connectivity/example/test_driver/test/integration_test.dart @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(amirh): Remove this once flutter_driver supports null safety. +// https://github.com/flutter/flutter/issues/71379 +// @dart = 2.9 + import 'dart:convert'; import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; diff --git a/packages/connectivity/connectivity/integration_test/connectivity_test.dart b/packages/connectivity/connectivity/integration_test/connectivity_test.dart index d48deae3403d..3177b66ea48a 100644 --- a/packages/connectivity/connectivity/integration_test/connectivity_test.dart +++ b/packages/connectivity/connectivity/integration_test/connectivity_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(cyanglaz): Remove once https://github.com/flutter/plugins/pull/3158 is landed. +// @dart = 2.9 + import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:connectivity/connectivity.dart'; diff --git a/packages/connectivity/connectivity/lib/connectivity.dart b/packages/connectivity/connectivity/lib/connectivity.dart index c9655365f772..0f30a9347b74 100644 --- a/packages/connectivity/connectivity/lib/connectivity.dart +++ b/packages/connectivity/connectivity/lib/connectivity.dart @@ -22,12 +22,12 @@ class Connectivity { if (_singleton == null) { _singleton = Connectivity._(); } - return _singleton; + return _singleton!; } Connectivity._(); - static Connectivity _singleton; + static Connectivity? _singleton; static ConnectivityPlatform get _platform => ConnectivityPlatform.instance; diff --git a/packages/connectivity/connectivity/pubspec.yaml b/packages/connectivity/connectivity/pubspec.yaml index 91d06373ab83..7ae03553a26c 100644 --- a/packages/connectivity/connectivity/pubspec.yaml +++ b/packages/connectivity/connectivity/pubspec.yaml @@ -2,7 +2,7 @@ name: connectivity description: Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS. homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity -version: 2.0.2 +version: 3.0.0-nullsafety.3 flutter: plugin: @@ -21,21 +21,24 @@ dependencies: flutter: sdk: flutter meta: ^1.0.5 - connectivity_platform_interface: ^1.0.2 - connectivity_macos: ^0.1.0 - connectivity_for_web: ^0.3.0 + connectivity_platform_interface: ^2.0.0-nullsafety.1 + #TODO(cyanglaz): re-endorse the below plugins when they have migrated to nnbd. + # https://github.com/flutter/flutter/issues/68669 + connectivity_macos: ^0.2.0-nullsafety + # connectivity_for_web: ^0.3.0 dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter + test: ^1.10.0-nullsafety.1 integration_test: path: ../../integration_test mockito: ^4.1.1 - plugin_platform_interface: ^1.0.0 - pedantic: ^1.8.0 + plugin_platform_interface: ^1.1.0-nullsafety.1 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/connectivity/connectivity/test/connectivity_test.dart b/packages/connectivity/connectivity/test/connectivity_test.dart index b7749ca7a6be..e83196546cd2 100644 --- a/packages/connectivity/connectivity/test/connectivity_test.dart +++ b/packages/connectivity/connectivity/test/connectivity_test.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - +// TODO(cyanglaz): Remove once Mockito is migrated to null safety. +// @dart = 2.9 import 'package:connectivity/connectivity.dart'; import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/connectivity/connectivity_macos/CHANGELOG.md b/packages/connectivity/connectivity_macos/CHANGELOG.md index 87910374bb8b..9261b0e789fe 100644 --- a/packages/connectivity/connectivity_macos/CHANGELOG.md +++ b/packages/connectivity/connectivity_macos/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.2.0-nullsafety + +* Update Dart SDK constraint. + +## 0.1.0+8 + +* Update Flutter SDK constraint. + ## 0.1.0+7 * Remove unused `test` dependency. diff --git a/packages/connectivity/connectivity_macos/pubspec.yaml b/packages/connectivity/connectivity_macos/pubspec.yaml index 2ab493072f8c..dd193f715c2a 100644 --- a/packages/connectivity/connectivity_macos/pubspec.yaml +++ b/packages/connectivity/connectivity_macos/pubspec.yaml @@ -3,7 +3,7 @@ description: macOS implementation of the connectivity plugin. # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.0+7 +version: 0.2.0-nullsafety homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_macos flutter: @@ -13,8 +13,8 @@ flutter: pluginClass: ConnectivityPlugin environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.10.0" dependencies: flutter: diff --git a/packages/connectivity/connectivity_platform_interface/CHANGELOG.md b/packages/connectivity/connectivity_platform_interface/CHANGELOG.md index e45f8f7e4d99..8e38341be42f 100644 --- a/packages/connectivity/connectivity_platform_interface/CHANGELOG.md +++ b/packages/connectivity/connectivity_platform_interface/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 1.0.7 + +* Update Flutter SDK constraint. + ## 1.0.6 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart b/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart index cfd9cf648a9c..8e9f0fd6af06 100644 --- a/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart +++ b/packages/connectivity/connectivity_platform_interface/lib/connectivity_platform_interface.dart @@ -50,17 +50,17 @@ abstract class ConnectivityPlatform extends PlatformInterface { } /// Obtains the wifi name (SSID) of the connected network - Future getWifiName() { + Future getWifiName() { throw UnimplementedError('getWifiName() has not been implemented.'); } /// Obtains the wifi BSSID of the connected network. - Future getWifiBSSID() { + Future getWifiBSSID() { throw UnimplementedError('getWifiBSSID() has not been implemented.'); } /// Obtains the IP address of the connected wifi network - Future getWifiIP() { + Future getWifiIP() { throw UnimplementedError('getWifiIP() has not been implemented.'); } diff --git a/packages/connectivity/connectivity_platform_interface/lib/src/method_channel_connectivity.dart b/packages/connectivity/connectivity_platform_interface/lib/src/method_channel_connectivity.dart index 87deaa21ea3b..b411b5bc069b 100644 --- a/packages/connectivity/connectivity_platform_interface/lib/src/method_channel_connectivity.dart +++ b/packages/connectivity/connectivity_platform_interface/lib/src/method_channel_connectivity.dart @@ -22,29 +22,29 @@ class MethodChannelConnectivity extends ConnectivityPlatform { EventChannel eventChannel = EventChannel('plugins.flutter.io/connectivity_status'); - Stream _onConnectivityChanged; + Stream? _onConnectivityChanged; /// Fires whenever the connectivity state changes. Stream get onConnectivityChanged { if (_onConnectivityChanged == null) { - _onConnectivityChanged = eventChannel - .receiveBroadcastStream() - .map((dynamic result) => result.toString()) - .map(parseConnectivityResult); + _onConnectivityChanged = + eventChannel.receiveBroadcastStream().map((dynamic result) { + return result != null ? result.toString() : ''; + }).map(parseConnectivityResult); } - return _onConnectivityChanged; + return _onConnectivityChanged!; } @override - Future checkConnectivity() { - return methodChannel - .invokeMethod('check') - .then(parseConnectivityResult); + Future checkConnectivity() async { + final String checkResult = + await methodChannel.invokeMethod('check') ?? ''; + return parseConnectivityResult(checkResult); } @override - Future getWifiName() async { - String wifiName = await methodChannel.invokeMethod('wifiName'); + Future getWifiName() async { + String? wifiName = await methodChannel.invokeMethod('wifiName'); // as Android might return , uniforming result // our iOS implementation will return null if (wifiName == '') { @@ -54,29 +54,31 @@ class MethodChannelConnectivity extends ConnectivityPlatform { } @override - Future getWifiBSSID() { + Future getWifiBSSID() { return methodChannel.invokeMethod('wifiBSSID'); } @override - Future getWifiIP() { + Future getWifiIP() { return methodChannel.invokeMethod('wifiIPAddress'); } @override Future requestLocationServiceAuthorization({ bool requestAlwaysLocationUsage = false, - }) { - return methodChannel.invokeMethod( - 'requestLocationServiceAuthorization', [ - requestAlwaysLocationUsage - ]).then(parseLocationAuthorizationStatus); + }) async { + final String requestLocationServiceResult = await methodChannel + .invokeMethod('requestLocationServiceAuthorization', + [requestAlwaysLocationUsage]) ?? + ''; + return parseLocationAuthorizationStatus(requestLocationServiceResult); } @override - Future getLocationServiceAuthorization() { - return methodChannel - .invokeMethod('getLocationServiceAuthorization') - .then(parseLocationAuthorizationStatus); + Future getLocationServiceAuthorization() async { + final String getLocationServiceResult = await methodChannel + .invokeMethod('getLocationServiceAuthorization') ?? + ''; + return parseLocationAuthorizationStatus(getLocationServiceResult); } } diff --git a/packages/connectivity/connectivity_platform_interface/pubspec.yaml b/packages/connectivity/connectivity_platform_interface/pubspec.yaml index 5bafcf9b806b..114915a10b60 100644 --- a/packages/connectivity/connectivity_platform_interface/pubspec.yaml +++ b/packages/connectivity/connectivity_platform_interface/pubspec.yaml @@ -3,19 +3,19 @@ description: A common platform interface for the connectivity plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_platform_interface # 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 +version: 2.0.0-nullsafety.1 dependencies: flutter: sdk: flutter - meta: ^1.0.5 - plugin_platform_interface: ^1.0.1 + meta: ^1.3.0-nullsafety.3 + plugin_platform_interface: ^1.1.0-nullsafety.1 dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart b/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart index 3d9c405c30ab..0c30530fc9ab 100644 --- a/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart +++ b/packages/connectivity/connectivity_platform_interface/test/method_channel_connectivity_test.dart @@ -12,7 +12,7 @@ void main() { group('$MethodChannelConnectivity', () { final List log = []; - MethodChannelConnectivity methodChannelConnectivity; + late MethodChannelConnectivity methodChannelConnectivity; setUp(() async { methodChannelConnectivity = MethodChannelConnectivity(); @@ -42,7 +42,7 @@ void main() { .setMockMethodCallHandler((MethodCall methodCall) async { switch (methodCall.method) { case 'listen': - await ServicesBinding.instance.defaultBinaryMessenger + await ServicesBinding.instance!.defaultBinaryMessenger .handlePlatformMessage( methodChannelConnectivity.eventChannel.name, methodChannelConnectivity.eventChannel.codec @@ -64,7 +64,7 @@ void main() { }); test('getWifiName', () async { - final String result = await methodChannelConnectivity.getWifiName(); + final String? result = await methodChannelConnectivity.getWifiName(); expect(result, '1337wifi'); expect( log, @@ -78,7 +78,7 @@ void main() { }); test('getWifiBSSID', () async { - final String result = await methodChannelConnectivity.getWifiBSSID(); + final String? result = await methodChannelConnectivity.getWifiBSSID(); expect(result, 'c0:ff:33:c0:d3:55'); expect( log, @@ -92,7 +92,7 @@ void main() { }); test('getWifiIP', () async { - final String result = await methodChannelConnectivity.getWifiIP(); + final String? result = await methodChannelConnectivity.getWifiIP(); expect(result, '127.0.0.1'); expect( log, diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 3b5ae7756a98..1716cb5ade7f 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.1.0+2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 0.1.0+1 + +- Update Flutter SDK constraint. + ## 0.1.0 - Initial open-source release \ No newline at end of file diff --git a/packages/cross_file/README.md b/packages/cross_file/README.md index f1ab89bc52f1..65bd41896184 100644 --- a/packages/cross_file/README.md +++ b/packages/cross_file/README.md @@ -24,7 +24,7 @@ final fileContent = await file.readAsString(); print('Content of the file: ${fileContent}'); // e.g. "Moto G (4)" ``` -You will find links to the API docs on the [pub page](https://pub.dartlang.org/packages/cross_file). +You will find links to the API docs on the [pub page](https://pub.dev/packages/cross_file). ## Getting Started diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index 40084d3d1ea0..0c7f30677aba 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -1,8 +1,7 @@ - name: cross_file description: An abstraction to allow working with files across multiple platforms. homepage: https://github.com/flutter/plugins/tree/master/packages/cross_file -version: 0.1.0 +version: 0.1.0+2 dependencies: flutter: @@ -17,4 +16,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" \ No newline at end of file + flutter: ">=1.22.0" diff --git a/packages/device_info/device_info/CHANGELOG.md b/packages/device_info/device_info/CHANGELOG.md index 06b327f4a198..910d265b7c3e 100644 --- a/packages/device_info/device_info/CHANGELOG.md +++ b/packages/device_info/device_info/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.0.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 1.0.1 + +* Update Flutter SDK constraint. + ## 1.0.0 * Announce 1.0.0. diff --git a/packages/device_info/device_info/README.md b/packages/device_info/device_info/README.md index 128bea5c2a17..98bfc15c7156 100644 --- a/packages/device_info/device_info/README.md +++ b/packages/device_info/device_info/README.md @@ -21,11 +21,11 @@ IosDeviceInfo iosInfo = await deviceInfo.iosInfo; print('Running on ${iosInfo.utsname.machine}'); // e.g. "iPod7,1" ``` -You will find links to the API docs on the [pub page](https://pub.dartlang.org/packages/device_info). +You will find links to the API docs on the [pub page](https://pub.dev/packages/device_info). ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/device_info/device_info/example/integration_test/device_info_test.dart b/packages/device_info/device_info/example/integration_test/device_info_test.dart index 2fd1d9a9a491..61c4396b0d8e 100644 --- a/packages/device_info/device_info/example/integration_test/device_info_test.dart +++ b/packages/device_info/device_info/example/integration_test/device_info_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(cyanglaz): Remove once https://github.com/flutter/plugins/pull/3158 is landed. +// @dart = 2.9 + import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:device_info/device_info.dart'; diff --git a/packages/device_info/device_info/example/lib/main.dart b/packages/device_info/device_info/example/lib/main.dart index 63912b37c3ce..805de1417f15 100644 --- a/packages/device_info/device_info/example/lib/main.dart +++ b/packages/device_info/device_info/example/lib/main.dart @@ -36,7 +36,7 @@ class _MyAppState extends State { } Future initPlatformState() async { - Map deviceData; + Map deviceData = {}; try { if (Platform.isAndroid) { diff --git a/packages/device_info/device_info/example/pubspec.yaml b/packages/device_info/device_info/example/pubspec.yaml index 58d54cba6d70..09567fd967b2 100644 --- a/packages/device_info/device_info/example/pubspec.yaml +++ b/packages/device_info/device_info/example/pubspec.yaml @@ -12,11 +12,11 @@ dev_dependencies: sdk: flutter integration_test: path: ../../../integration_test - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 flutter: uses-material-design: true environment: - sdk: ">=2.1.0<3.0.0" + sdk: ">=2.10.0-56.0.dev <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/device_info/device_info/example/test_driver/integration_test.dart b/packages/device_info/device_info/example/test_driver/integration_test.dart index 7a2c21338786..13327bb884c9 100644 --- a/packages/device_info/device_info/example/test_driver/integration_test.dart +++ b/packages/device_info/device_info/example/test_driver/integration_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(cyanglaz): Remove once https://github.com/flutter/flutter/issues/59879 is fixed. +// @dart = 2.9 + import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/packages/device_info/device_info/lib/device_info.dart b/packages/device_info/device_info/lib/device_info.dart index f63730c4323f..bccc3d2fbfa6 100644 --- a/packages/device_info/device_info/lib/device_info.dart +++ b/packages/device_info/device_info/lib/device_info.dart @@ -15,7 +15,7 @@ class DeviceInfoPlugin { DeviceInfoPlugin(); /// This information does not change from call to call. Cache it. - AndroidDeviceInfo _cachedAndroidDeviceInfo; + AndroidDeviceInfo? _cachedAndroidDeviceInfo; /// Information derived from `android.os.Build`. /// @@ -25,7 +25,7 @@ class DeviceInfoPlugin { await DeviceInfoPlatform.instance.androidInfo(); /// This information does not change from call to call. Cache it. - IosDeviceInfo _cachedIosDeviceInfo; + IosDeviceInfo? _cachedIosDeviceInfo; /// Information derived from `UIDevice`. /// diff --git a/packages/device_info/device_info/pubspec.yaml b/packages/device_info/device_info/pubspec.yaml index 5b00750fcbbc..1a222869a632 100644 --- a/packages/device_info/device_info/pubspec.yaml +++ b/packages/device_info/device_info/pubspec.yaml @@ -2,7 +2,10 @@ name: device_info description: Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on. homepage: https://github.com/flutter/plugins/tree/master/packages/device_info -version: 1.0.0 +# 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump +# the version to 2.0.0. +# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 +version: 2.0.0-nullsafety.2 flutter: plugin: @@ -16,14 +19,13 @@ flutter: dependencies: flutter: sdk: flutter - device_info_platform_interface: ^1.0.0 - + device_info_platform_interface: ^2.0.0-nullsafety.1 dev_dependencies: - test: ^1.3.0 + test: ^1.10.0-nullsafety.1 flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0<3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/device_info/device_info_platform_interface/CHANGELOG.md b/packages/device_info/device_info_platform_interface/CHANGELOG.md index 8a7eb6c46be3..d4bc81e0f0aa 100644 --- a/packages/device_info/device_info_platform_interface/CHANGELOG.md +++ b/packages/device_info/device_info_platform_interface/CHANGELOG.md @@ -1,3 +1,20 @@ +## 2.0.0-nullsafety.2 + +* Make `baseOS`, `previewSdkInt`, and `securityPatch` nullable types. +* Remove default values for non-nullable types. + +## 2.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 1.0.2 + +- Update Flutter SDK constraint. + ## 1.0.1 - Documentation typo fixed. diff --git a/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart index 5b326cc5350a..4fb940c3effa 100644 --- a/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart +++ b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart @@ -8,28 +8,28 @@ class AndroidDeviceInfo { /// Android device Info class. AndroidDeviceInfo({ - this.version, - this.board, - this.bootloader, - this.brand, - this.device, - this.display, - this.fingerprint, - this.hardware, - this.host, - this.id, - this.manufacturer, - this.model, - this.product, - List supported32BitAbis, - List supported64BitAbis, - List supportedAbis, - this.tags, - this.type, - this.isPhysicalDevice, - this.androidId, - List systemFeatures, - }) : supported32BitAbis = List.unmodifiable(supported32BitAbis), + required this.version, + required this.board, + required this.bootloader, + required this.brand, + required this.device, + required this.display, + required this.fingerprint, + required this.hardware, + required this.host, + required this.id, + required this.manufacturer, + required this.model, + required this.product, + required List supported32BitAbis, + required List supported64BitAbis, + required List supportedAbis, + required this.tags, + required this.type, + required this.isPhysicalDevice, + required this.androidId, + required List systemFeatures, + }) : supported32BitAbis = List.unmodifiable(supported32BitAbis), supported64BitAbis = List.unmodifiable(supported64BitAbis), supportedAbis = List.unmodifiable(supportedAbis), systemFeatures = List.unmodifiable(systemFeatures); @@ -113,28 +113,28 @@ class AndroidDeviceInfo { /// Deserializes from the message received from [_kChannel]. static AndroidDeviceInfo fromMap(Map map) { return AndroidDeviceInfo( - version: AndroidBuildVersion._fromMap( - map['version']?.cast() ?? {}), - board: map['board'], - bootloader: map['bootloader'], - brand: map['brand'], - device: map['device'], - display: map['display'], - fingerprint: map['fingerprint'], - hardware: map['hardware'], - host: map['host'], - id: map['id'], - manufacturer: map['manufacturer'], - model: map['model'], - product: map['product'], - supported32BitAbis: _fromList(map['supported32BitAbis'] ?? []), - supported64BitAbis: _fromList(map['supported64BitAbis'] ?? []), - supportedAbis: _fromList(map['supportedAbis'] ?? []), - tags: map['tags'], - type: map['type'], - isPhysicalDevice: map['isPhysicalDevice'], - androidId: map['androidId'], - systemFeatures: _fromList(map['systemFeatures'] ?? []), + version: + AndroidBuildVersion._fromMap(map['version']!.cast()), + board: map['board']!, + bootloader: map['bootloader']!, + brand: map['brand']!, + device: map['device']!, + display: map['display']!, + fingerprint: map['fingerprint']!, + hardware: map['hardware']!, + host: map['host']!, + id: map['id']!, + manufacturer: map['manufacturer']!, + model: map['model']!, + product: map['product']!, + supported32BitAbis: _fromList(map['supported32BitAbis']!), + supported64BitAbis: _fromList(map['supported64BitAbis']!), + supportedAbis: _fromList(map['supportedAbis']!), + tags: map['tags']!, + type: map['type']!, + isPhysicalDevice: map['isPhysicalDevice']!, + androidId: map['androidId']!, + systemFeatures: _fromList(map['systemFeatures']!), ); } @@ -152,16 +152,25 @@ class AndroidDeviceInfo { class AndroidBuildVersion { AndroidBuildVersion._({ this.baseOS, - this.codename, - this.incremental, this.previewSdkInt, - this.release, - this.sdkInt, this.securityPatch, + required this.codename, + required this.incremental, + required this.release, + required this.sdkInt, }); /// The base OS build the product is based on. - final String baseOS; + /// This is only available on Android 6.0 or above. + String? baseOS; + + /// The developer preview revision of a prerelease SDK. + /// This is only available on Android 6.0 or above. + int? previewSdkInt; + + /// The user-visible security patch level. + /// This is only available on Android 6.0 or above. + final String? securityPatch; /// The current development codename, or the string "REL" if this is a release build. final String codename; @@ -169,9 +178,6 @@ class AndroidBuildVersion { /// The internal value used by the underlying source control to represent this build. final String incremental; - /// The developer preview revision of a prerelease SDK. - final int previewSdkInt; - /// The user-visible version string. final String release; @@ -180,19 +186,16 @@ class AndroidBuildVersion { /// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html final int sdkInt; - /// The user-visible security patch level. - final String securityPatch; - /// Deserializes from the map message received from [_kChannel]. static AndroidBuildVersion _fromMap(Map map) { return AndroidBuildVersion._( baseOS: map['baseOS'], - codename: map['codename'], - incremental: map['incremental'], previewSdkInt: map['previewSdkInt'], - release: map['release'], - sdkInt: map['sdkInt'], securityPatch: map['securityPatch'], + codename: map['codename']!, + incremental: map['incremental']!, + release: map['release']!, + sdkInt: map['sdkInt']!, ); } } diff --git a/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart index d41202492101..eb6e5874073b 100644 --- a/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart +++ b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart @@ -8,14 +8,14 @@ class IosDeviceInfo { /// IOS device info class. IosDeviceInfo({ - this.name, - this.systemName, - this.systemVersion, - this.model, - this.localizedModel, - this.identifierForVendor, - this.isPhysicalDevice, - this.utsname, + required this.name, + required this.systemName, + required this.systemVersion, + required this.model, + required this.localizedModel, + required this.identifierForVendor, + required this.isPhysicalDevice, + required this.utsname, }); /// Device name. @@ -45,15 +45,14 @@ class IosDeviceInfo { /// Deserializes from the map message received from [_kChannel]. static IosDeviceInfo fromMap(Map map) { return IosDeviceInfo( - name: map['name'], - systemName: map['systemName'], - systemVersion: map['systemVersion'], - model: map['model'], - localizedModel: map['localizedModel'], - identifierForVendor: map['identifierForVendor'], + name: map['name']!, + systemName: map['systemName']!, + systemVersion: map['systemVersion']!, + model: map['model']!, + localizedModel: map['localizedModel']!, + identifierForVendor: map['identifierForVendor']!, isPhysicalDevice: map['isPhysicalDevice'] == 'true', - utsname: - IosUtsname._fromMap(map['utsname']?.cast() ?? {}), + utsname: IosUtsname._fromMap(map['utsname']!.cast()), ); } } @@ -62,11 +61,11 @@ class IosDeviceInfo { /// See http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html for details. class IosUtsname { IosUtsname._({ - this.sysname, - this.nodename, - this.release, - this.version, - this.machine, + required this.sysname, + required this.nodename, + required this.release, + required this.version, + required this.machine, }); /// Operating system name. @@ -87,11 +86,11 @@ class IosUtsname { /// Deserializes from the map message received from [_kChannel]. static IosUtsname _fromMap(Map map) { return IosUtsname._( - sysname: map['sysname'], - nodename: map['nodename'], - release: map['release'], - version: map['version'], - machine: map['machine'], + sysname: map['sysname']!, + nodename: map['nodename']!, + release: map['release']!, + version: map['version']!, + machine: map['machine']!, ); } } diff --git a/packages/device_info/device_info_platform_interface/pubspec.yaml b/packages/device_info/device_info_platform_interface/pubspec.yaml index 656e5b24c373..ca72cc753b63 100644 --- a/packages/device_info/device_info_platform_interface/pubspec.yaml +++ b/packages/device_info/device_info_platform_interface/pubspec.yaml @@ -3,20 +3,20 @@ description: A common platform interface for the device_info plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/device_info # 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.1 +version: 2.0.0-nullsafety.2 dependencies: flutter: sdk: flutter - meta: ^1.1.8 - plugin_platform_interface: ^1.0.2 + meta: ^1.3.0-nullsafety.3 + plugin_platform_interface: ^1.1.0-nullsafety.1 dev_dependencies: flutter_test: sdk: flutter - mockito: ^4.1.1 - pedantic: ^1.8.0 + test: ^1.10.0-nullsafety.1 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.9.1+hotfix.4" diff --git a/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart index 1da52e2cf39f..15963854ab12 100644 --- a/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart +++ b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(cyanglaz): Remove once https://github.com/flutter/flutter/issues/59879 is fixed. +// @dart = 2.9 + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:device_info_platform_interface/device_info_platform_interface.dart'; - import 'package:device_info_platform_interface/method_channel/method_channel_device_info.dart'; void main() { @@ -23,11 +24,68 @@ void main() { switch (methodCall.method) { case 'getAndroidDeviceInfo': return ({ - "brand": "Google", + "version": { + "securityPatch": "2018-09-05", + "sdkInt": 28, + "release": "9", + "previewSdkInt": 0, + "incremental": "5124027", + "codename": "REL", + "baseOS": "", + }, + "board": "goldfish_x86_64", + "bootloader": "unknown", + "brand": "google", + "device": "generic_x86_64", + "display": "PSR1.180720.075", + "fingerprint": + "google/sdk_gphone_x86_64/generic_x86_64:9/PSR1.180720.075/5124027:user/release-keys", + "hardware": "ranchu", + "host": "abfarm730", + "id": "PSR1.180720.075", + "manufacturer": "Google", + "model": "Android SDK built for x86_64", + "product": "sdk_gphone_x86_64", + "supported32BitAbis": [ + "x86", + ], + "supported64BitAbis": [ + "x86_64", + ], + "supportedAbis": [ + "x86_64", + "x86", + ], + "tags": "release-keys", + "type": "user", + "isPhysicalDevice": false, + "androidId": "f47571f3b4648f45", + "systemFeatures": [ + "android.hardware.sensor.proximity", + "android.software.adoptable_storage", + "android.hardware.sensor.accelerometer", + "android.hardware.faketouch", + "android.software.backup", + "android.hardware.touchscreen", + ], }); case 'getIosDeviceInfo': return ({ - "name": "iPhone 10", + "name": "iPhone 13", + "systemName": "iOS", + "systemVersion": "13.0", + "model": "iPhone", + "localizedModel": "iPhone", + "identifierForVendor": "88F59280-55AD-402C-B922-3203B4794C06", + "isPhysicalDevice": false, + "utsname": { + "sysname": "Darwin", + "nodename": "host", + "release": "19.6.0", + "version": + "Darwin Kernel Version 19.6.0: Thu Jun 18 20:49:00 PDT 2020; root:xnu-6153.141.1~1/RELEASE_X86_64", + "machine": "x86_64", + } }); default: return null; @@ -38,12 +96,66 @@ void main() { test("androidInfo", () async { final AndroidDeviceInfo result = await methodChannelDeviceInfo.androidInfo(); - expect(result.brand, "Google"); + + expect(result.version.securityPatch, "2018-09-05"); + expect(result.version.sdkInt, 28); + expect(result.version.release, "9"); + expect(result.version.previewSdkInt, 0); + expect(result.version.incremental, "5124027"); + expect(result.version.codename, "REL"); + expect(result.board, "goldfish_x86_64"); + expect(result.bootloader, "unknown"); + expect(result.brand, "google"); + expect(result.device, "generic_x86_64"); + expect(result.display, "PSR1.180720.075"); + expect(result.fingerprint, + "google/sdk_gphone_x86_64/generic_x86_64:9/PSR1.180720.075/5124027:user/release-keys"); + expect(result.hardware, "ranchu"); + expect(result.host, "abfarm730"); + expect(result.id, "PSR1.180720.075"); + expect(result.manufacturer, "Google"); + expect(result.model, "Android SDK built for x86_64"); + expect(result.product, "sdk_gphone_x86_64"); + expect(result.supported32BitAbis, [ + "x86", + ]); + expect(result.supported64BitAbis, [ + "x86_64", + ]); + expect(result.supportedAbis, [ + "x86_64", + "x86", + ]); + expect(result.tags, "release-keys"); + expect(result.type, "user"); + expect(result.isPhysicalDevice, false); + expect(result.androidId, "f47571f3b4648f45"); + expect(result.systemFeatures, [ + "android.hardware.sensor.proximity", + "android.software.adoptable_storage", + "android.hardware.sensor.accelerometer", + "android.hardware.faketouch", + "android.software.backup", + "android.hardware.touchscreen", + ]); }); test("iosInfo", () async { final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo(); - expect(result.name, "iPhone 10"); + expect(result.name, "iPhone 13"); + expect(result.systemName, "iOS"); + expect(result.systemVersion, "13.0"); + expect(result.model, "iPhone"); + expect(result.localizedModel, "iPhone"); + expect( + result.identifierForVendor, "88F59280-55AD-402C-B922-3203B4794C06"); + expect(result.isPhysicalDevice, false); + expect(result.utsname.sysname, "Darwin"); + expect(result.utsname.nodename, "host"); + expect(result.utsname.release, "19.6.0"); + expect(result.utsname.version, + "Darwin Kernel Version 19.6.0: Thu Jun 18 20:49:00 PDT 2020; root:xnu-6153.141.1~1/RELEASE_X86_64"); + expect(result.utsname.machine, "x86_64"); }); }); } diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index 0ab068056df9..fe43202b7654 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+9 + +* Update Flutter SDK constraint. + ## 0.0.1+8 * Android: Handle deprecation & unchecked warning as error. diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index 39b88cb98940..e7e1f6691ed3 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -1,11 +1,11 @@ name: espresso description: Java classes for testing Flutter apps using Espresso. -version: 0.0.1+8 +version: 0.0.1+9 homepage: https://github.com/flutter/plugins/espresso environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" dependencies: flutter: diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 8dab88a33cef..92136485a447 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.0+1 + +* Update Flutter SDK constraint. + ## 0.7.0 * Initial Open Source release. diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 5a90f048c314..f095ba1f6a36 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -1,7 +1,7 @@ name: file_selector description: Flutter plugin for opening and saving files. homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector -version: 0.7.0 +version: 0.7.0+1 dependencies: flutter: @@ -18,4 +18,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index 7f1d5732ec9b..0d6d6d08c298 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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. diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index a0a3d28922fe..2d0b7c954ece 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the file_selector plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_platform_interface # 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.2 +version: 1.0.3 dependencies: flutter: @@ -22,4 +22,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + flutter: ">=1.9.1+hotfix.4" diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md index 401be5f5278a..e1804c5cc7f5 100644 --- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.0.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety.1 + +* Fix example app SDK. + +## 2.0.0-nullsafety + +* Bump Dart SDK. + +## 1.0.12 + +* Update Flutter SDK constraint. + ## 1.0.11 * Keep handling deprecated Android v1 classes for backward compatibility. diff --git a/packages/flutter_plugin_android_lifecycle/README.md b/packages/flutter_plugin_android_lifecycle/README.md index 25f4d9efd056..3290140f4e5e 100644 --- a/packages/flutter_plugin_android_lifecycle/README.md +++ b/packages/flutter_plugin_android_lifecycle/README.md @@ -1,6 +1,6 @@ # Flutter Android Lifecycle Plugin -[![pub package](https://img.shields.io/pub/v/flutter_plugin_android_lifecycle.svg)](https://pub.dartlang.org/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) A Flutter plugin for Android to allow other Flutter plugins to access Android `Lifecycle` objects in the plugin's binding. @@ -11,7 +11,7 @@ major version of the Android `Lifecycle` API they expect. ## Installation -Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). +Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ## Example diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml index a040dbe95d92..2532ab047dcc 100644 --- a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the flutter_plugin_android_lifecycle plugin publish_to: 'none' environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" dependencies: flutter: diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml index a671381b3f47..d94237c2101a 100644 --- a/packages/flutter_plugin_android_lifecycle/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -1,11 +1,11 @@ name: flutter_plugin_android_lifecycle description: Flutter plugin for accessing an Android Lifecycle within other plugins. -version: 1.0.11 +version: 2.0.0-nullsafety.2 homepage: https://github.com/flutter/plugins/tree/master/packages/flutter_plugin_android_lifecycle environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13" dependencies: flutter: diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 12e9ab4b55f7..7783f3042dde 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md index cbc0ccbf62b8..6bb11a8da793 100644 --- a/packages/google_maps_flutter/google_maps_flutter/README.md +++ b/packages/google_maps_flutter/google_maps_flutter/README.md @@ -1,12 +1,12 @@ # Google Maps for Flutter -[![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dartlang.org/packages/google_maps_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. ## Usage -To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +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 diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj index f6a2d6ec291a..75392aeb82e5 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,14 +34,12 @@ 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 = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; 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 = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; 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 = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -230,7 +218,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -270,9 +258,12 @@ 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; @@ -285,9 +276,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -331,7 +325,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -388,7 +381,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 06d5fb2f4257..7d480ebf74f2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter -version: 1.0.7 +version: 1.0.9 dependencies: flutter: @@ -33,4 +33,4 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" + flutter: ">=1.22.0" diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 0586ac414d97..b40fc9d40e5b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index a2b5ff56fee1..633478c5d636 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the google_maps_flutter plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter_platform_interface # 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.5 +version: 1.0.6 dependencies: flutter: @@ -20,4 +20,4 @@ dev_dependencies: environment: sdk: ">=2.3.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + flutter: ">=1.9.1+hotfix.4" diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index dcafa12a2c13..2d03ab254bc0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+9 + +* Update Flutter SDK constraint. + ## 0.1.0+8 * Update `package:google_maps_flutter_platform_interface` to `^1.0.5`. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 237415318aac..b41e24c25357 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -1,7 +1,7 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.1.0+8 +version: 0.1.0+9 flutter: plugin: @@ -33,4 +33,4 @@ dev_dependencies: environment: sdk: ">=2.3.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md index 3425ff6c34b0..25539436f8fa 100644 --- a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.3 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 1.0.2 + +* Update Flutter SDK constraint. + ## 1.0.1 * Update android compileSdkVersion to 29. diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md index 689071dcfe8d..a7ad235b71d5 100755 --- a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md @@ -5,4 +5,4 @@ Demonstrates how to use the google_sign_in plugin with the `googleapis` package. ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml index c978ce446714..aecd5a9569be 100644 --- a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml @@ -6,7 +6,7 @@ name: extension_google_sign_in_as_googleapis_auth description: A bridge package between google_sign_in and googleapis_auth, to create Authenticated Clients from google_sign_in user credentials. -version: 1.0.1 +version: 1.0.3 homepage: https://github.com/flutter/plugins/google_sign_in/extension_google_sign_in_as_googleapis_auth dependencies: @@ -25,4 +25,4 @@ dev_dependencies: environment: sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 7f4bfddfa45a..8a4dd6bc817e 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md index fc877171c4d2..61c4380cdcb7 100755 --- a/packages/google_sign_in/google_sign_in/README.md +++ b/packages/google_sign_in/google_sign_in/README.md @@ -1,6 +1,6 @@ # google_sign_in -[![pub package](https://img.shields.io/pub/v/google_sign_in.svg)](https://pub.dartlang.org/packages/google_sign_in) +[![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/). @@ -63,7 +63,7 @@ plugin could be an option. ## Usage ### Import the package -To use this plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/google_sign_in#pub-pkg-tab-installing). +To use this plugin, follow the [plugin installation instructions](https://pub.dev/packages/google_sign_in#pub-pkg-tab-installing). ### Use the plugin Add the following import to your Dart code: diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index a63091b945b2..b99b231adb9d 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -2,7 +2,7 @@ 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. homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in -version: 4.5.6 +version: 4.5.8 flutter: plugin: @@ -39,4 +39,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart index bc709197d77d..7305800296f9 100755 --- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 +// @dart = 2.9 import 'dart:async'; diff --git a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md index aa8ad2cff80f..e69e912195bf 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.3 + +* Update Flutter SDK constraint. + ## 1.1.2 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml index 7bc63d84110c..8edeba0072a8 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the google_sign_in plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_platform_interface # 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.1.2 +version: 1.1.3 dependencies: flutter: @@ -19,4 +19,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md index d71badc53bfc..d1353f723fd5 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.2+1 + +* Update Flutter SDK constraint. + ## 0.9.2 * Throw PlatformExceptions from where the GMaps SDK may throw exceptions: `init()` and `signIn()`. diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml index 70758ac6830d..ac9d36bd15be 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -2,7 +2,7 @@ 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. homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web -version: 0.9.2 +version: 0.9.2+1 flutter: plugin: @@ -31,4 +31,4 @@ dev_dependencies: environment: sdk: ">=2.6.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 970ddf1ed7ed..4c1553a8ccaf 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,15 @@ +## 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. diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index 2e062a024597..106d78b630b9 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -1,13 +1,13 @@ # Image Picker plugin for Flutter -[![pub package](https://img.shields.io/pub/v/image_picker.svg)](https://pub.dartlang.org/packages/image_picker) +[![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. ## Installation -First, add `image_picker` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +First, add `image_picker` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### iOS diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 00fdec245aaf..8d260f31b055 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -146,10 +146,10 @@ - (void)showCamera { animated:YES completion:nil]; } else { - [[[UIAlertView alloc] initWithTitle:@"Error" - message:@"Camera not available." + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil) + message:NSLocalizedString(@"Camera not available.", nil) delegate:nil - cancelButtonTitle:@"OK" + cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] show]; self.result(nil); self.result = nil; diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 226602a99403..29baaa0de5d5 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.6.7+15 +version: 0.6.7+18 flutter: plugin: @@ -29,4 +29,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 604314240a1e..4c452ee78de9 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.1.0+3 + +* Update Flutter SDK constraint. + # 0.1.0+2 * Adds Video MIME Types for the safari browser for acception diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index 32e89437415e..b7e079b39ce0 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/i # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.0+2 +version: 0.1.0+3 flutter: plugin: @@ -29,4 +29,4 @@ dev_dependencies: environment: sdk: ">=2.5.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 1ed45028f513..efcef0146cdc 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart index 0faf531f3f75..ee5145009dc7 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart @@ -24,7 +24,7 @@ class PickedFile extends PickedFileBase { if (_initBytes != null) { return Future.value(UnmodifiableUint8ListView(_initBytes)); } - return http.readBytes(path); + return http.readBytes(Uri.parse(path)); } @override diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index 39a65284e247..7943a2a3eccd 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the image_picker plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_platform_interface # 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.1.3 +version: 1.1.5 dependencies: flutter: @@ -20,4 +20,4 @@ dev_dependencies: environment: sdk: ">=2.5.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 6207da9b403c..0a5794f931a8 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/in_app_purchase/README.md b/packages/in_app_purchase/README.md index 021811842b2f..431f2810c165 100644 --- a/packages/in_app_purchase/README.md +++ b/packages/in_app_purchase/README.md @@ -181,7 +181,7 @@ WARNING! Failure to call `InAppPurchaseConnection.completePurchase` and get a su ## Development This plugin uses -[json_serializable](https://pub.dartlang.org/packages/json_serializable) for the +[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`. diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index eda865e487f7..5e5ef2447278 100644 --- a/packages/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/pubspec.yaml @@ -1,7 +1,7 @@ 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. homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase -version: 0.3.4+16 +version: 0.3.4+18 dependencies: async: ^2.0.8 @@ -37,4 +37,4 @@ flutter: environment: sdk: ">=2.3.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/integration_test/CHANGELOG.md b/packages/integration_test/CHANGELOG.md index 912e7404e906..4bbfe591d6cc 100644 --- a/packages/integration_test/CHANGELOG.md +++ b/packages/integration_test/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.2 + +* Update Flutter SDK constraint. + ## 1.0.1 * Remove usages of deprecated `List` constructor. diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index bef1ceb0a2e6..997faa79e1ca 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -1,11 +1,11 @@ name: integration_test description: Runs tests that use the flutter_test API as integration tests. -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/flutter/plugins/tree/master/packages/integration_test environment: sdk: ">=2.2.2 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" dependencies: flutter: diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index a83f22ec7571..4b11b40f5510 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2+4 + +* Update Flutter SDK constraint. + ## 0.1.2+3 * Remove no-op android folder in the example app. diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 482097a515b8..7049b62cf00a 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -1,11 +1,11 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. -version: 0.1.2+3 +version: 0.1.2+4 homepage: https://github.com/flutter/plugins/tree/master/packages/ios_platform_images/ios_platform_images environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" dependencies: flutter: @@ -29,7 +29,6 @@ flutter: platforms: ios: pluginClass: IosPlatformImagesPlugin - # To add assets to your plugin package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg @@ -40,7 +39,6 @@ flutter: # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your plugin package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 96388a76ab4f..863d72ed1da4 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,3 +1,19 @@ +## 1.0.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 1.0.0-nullsafety.1 + +* Update README for Android Integration. + +## 1.0.0-nullsafety + +* Migrate to null safety. + +## 0.6.3+5 + +* Update Flutter SDK constraint. + ## 0.6.3+4 * Update Dart SDK constraint in example. @@ -174,4 +190,4 @@ ## 0.0.1 -* Initial release of local authentication plugin. +* Initial release of local authentication plugin. \ No newline at end of file diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md index ca2aa49bed23..516561be4230 100644 --- a/packages/local_auth/README.md +++ b/packages/local_auth/README.md @@ -142,6 +142,42 @@ opposed to Activity. This can be easily done by switching to use `FlutterFragmentActivity` as opposed to `FlutterActivity` in your manifest (or your own Activity class if you are extending the base class). +Update your MainActivity.java: + +```java +import android.os.Bundle; +import io.flutter.app.FlutterFragmentActivity; +import io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin; +import io.flutter.plugins.localauth.LocalAuthPlugin; + +public class MainActivity extends FlutterFragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FlutterAndroidLifecyclePlugin.registerWith( + registrarFor( + "io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin")); + LocalAuthPlugin.registerWith(registrarFor("io.flutter.plugins.localauth.LocalAuthPlugin")); + } +} +``` + +OR + +Update your MainActivity.kt: + +```kotlin +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterFragmentActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine) + } +} +``` + Update your project's `AndroidManifest.xml` file to include the `USE_FINGERPRINT` permissions: @@ -170,6 +206,6 @@ app resumes. ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/local_auth/lib/auth_strings.dart b/packages/local_auth/lib/auth_strings.dart index a8f34f88723c..3afc23827d98 100644 --- a/packages/local_auth/lib/auth_strings.dart +++ b/packages/local_auth/lib/auth_strings.dart @@ -25,14 +25,14 @@ class AndroidAuthMessages { this.goToSettingsDescription, }); - final String fingerprintHint; - final String fingerprintNotRecognized; - final String fingerprintSuccess; - final String cancelButton; - final String signInTitle; - final String fingerprintRequiredTitle; - final String goToSettingsButton; - final String goToSettingsDescription; + final String? fingerprintHint; + final String? fingerprintNotRecognized; + final String? fingerprintSuccess; + final String? cancelButton; + final String? signInTitle; + final String? fingerprintRequiredTitle; + final String? goToSettingsButton; + final String? goToSettingsDescription; Map get args { return { @@ -62,10 +62,10 @@ class IOSAuthMessages { this.cancelButton, }); - final String lockOut; - final String goToSettingsButton; - final String goToSettingsDescription; - final String cancelButton; + final String? lockOut; + final String? goToSettingsButton; + final String? goToSettingsDescription; + final String? cancelButton; Map get args { return { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index b2b03b920d64..f1dbdd4840a8 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -67,7 +67,7 @@ class LocalAuthentication { /// [PlatformException] with error code [otherOperatingSystem] on the iOS /// simulator. Future authenticateWithBiometrics({ - @required String localizedReason, + required String localizedReason, bool useErrorDialogs = true, bool stickyAuth = false, AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), @@ -92,8 +92,9 @@ class LocalAuthentication { 'operating systems.', details: 'Your operating system is ${_platform.operatingSystem}'); } - return await _channel.invokeMethod( - 'authenticateWithBiometrics', args); + final bool? result = + await _channel.invokeMethod('authenticateWithBiometrics', args); + return result!; } /// Returns true if auth was cancelled successfully. @@ -101,18 +102,20 @@ class LocalAuthentication { /// Returns false if there was some error or no auth in progress. /// /// Returns [Future] bool true or false: - Future stopAuthentication() { + Future stopAuthentication() async { if (_platform.isAndroid) { - return _channel.invokeMethod('stopAuthentication'); + final bool? result = + await _channel.invokeMethod('stopAuthentication'); + return result!; } - return Future.sync(() => true); + return true; } /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: Future get canCheckBiometrics async => - (await _channel.invokeListMethod('getAvailableBiometrics')) + (await _channel.invokeListMethod('getAvailableBiometrics'))! .isNotEmpty; /// Returns a list of enrolled biometrics @@ -122,10 +125,10 @@ class LocalAuthentication { /// - BiometricType.fingerprint /// - BiometricType.iris (not yet implemented) Future> getAvailableBiometrics() async { - final List result = - (await _channel.invokeListMethod('getAvailableBiometrics')); + final List? result = + await _channel.invokeListMethod('getAvailableBiometrics'); final List biometrics = []; - result.forEach((String value) { + result!.forEach((String value) { switch (value) { case 'face': biometrics.add(BiometricType.face); diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 6ba77aca6679..444eec2efa53 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth description: Flutter plugin for Android and iOS device authentication sensors such as Fingerprint Reader and Touch ID. homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth -version: 0.6.3+4 +version: 1.0.0-nullsafety.2 flutter: plugin: @@ -16,10 +16,10 @@ flutter: dependencies: flutter: sdk: flutter - meta: ^1.0.5 - intl: ">=0.15.1 <0.17.0" - platform: ">=2.0.0 <4.0.0" - flutter_plugin_android_lifecycle: ^1.0.2 + meta: ^1.3.0-nullsafety.3 + intl: ^0.17.0-nullsafety.2 + platform: ^3.0.0-nullsafety.4 + flutter_plugin_android_lifecycle: ^2.0.0-nullsafety dev_dependencies: integration_test: @@ -28,8 +28,8 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart index 205c5f785708..52b8dbf21f72 100644 --- a/packages/local_auth/test/local_auth_test.dart +++ b/packages/local_auth/test/local_auth_test.dart @@ -19,7 +19,7 @@ void main() { ); final List log = []; - LocalAuthentication localAuthentication; + late LocalAuthentication localAuthentication; setUp(() { channel.setMockMethodCallHandler((MethodCall methodCall) { diff --git a/packages/package_info/CHANGELOG.md b/packages/package_info/CHANGELOG.md index 21319fe4fbf1..ebb95c1da17e 100644 --- a/packages/package_info/CHANGELOG.md +++ b/packages/package_info/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.3+3 + +* Update Flutter SDK constraint. + ## 0.4.3+2 * Remove unused `test` dependency. diff --git a/packages/package_info/pubspec.yaml b/packages/package_info/pubspec.yaml index cb46ca66b9a4..884a71659a48 100644 --- a/packages/package_info/pubspec.yaml +++ b/packages/package_info/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/package_info # 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.4.3+2 +version: 0.4.3+3 flutter: plugin: @@ -33,4 +33,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index 646a2e36bc9e..f0e3cd9bfde8 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index e8d97e0106a3..0c80a5b34075 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -1,13 +1,13 @@ # path_provider -[![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dartlang.org/packages/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 iOS, Android, Linux and MacOS. Not all methods are supported on all platforms. ## Usage -To use this plugin, add `path_provider` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +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 diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index b3960941acff..15f9c57a0c98 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -1,7 +1,7 @@ name: path_provider description: Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories. homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider -version: 1.6.24 +version: 1.6.26 flutter: plugin: @@ -40,4 +40,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md index bf043c6e6954..ee382b04710b 100644 --- a/packages/path_provider/path_provider_linux/CHANGELOG.md +++ b/packages/path_provider/path_provider_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1+3 + +* Update Flutter SDK constraint. + ## 0.1.1+2 * Log errors in the example when calls to the `path_provider` fail. diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml index fac82d24829f..adabbdd45246 100644 --- a/packages/path_provider/path_provider_linux/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/pubspec.yaml @@ -1,6 +1,6 @@ name: path_provider_linux description: linux implementation of the path_provider plugin -version: 0.1.1+2 +version: 0.1.1+3 homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux flutter: @@ -12,7 +12,7 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" dependencies: path: ^1.6.4 diff --git a/packages/path_provider/path_provider_macos/CHANGELOG.md b/packages/path_provider/path_provider_macos/CHANGELOG.md index ba2a38e563d6..d9be6859e125 100644 --- a/packages/path_provider/path_provider_macos/CHANGELOG.md +++ b/packages/path_provider/path_provider_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.4+7 + +* Update Flutter SDK constraint. + ## 0.0.4+6 * Remove unused `test` dependency. diff --git a/packages/path_provider/path_provider_macos/pubspec.yaml b/packages/path_provider/path_provider_macos/pubspec.yaml index 69241491d29d..05f03a7930ba 100644 --- a/packages/path_provider/path_provider_macos/pubspec.yaml +++ b/packages/path_provider/path_provider_macos/pubspec.yaml @@ -3,7 +3,7 @@ description: macOS implementation of the path_provider plugin # 0.0.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.0.4+6 +version: 0.0.4+7 homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_macos flutter: @@ -14,7 +14,7 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md index 744764c3215a..97121268c790 100644 --- a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md +++ b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 1.0.5 + +* Update Flutter SDK constraint. + ## 1.0.4 * Remove unused `test` dependency. diff --git a/packages/path_provider/path_provider_platform_interface/lib/path_provider_platform_interface.dart b/packages/path_provider/path_provider_platform_interface/lib/path_provider_platform_interface.dart index 4f796aaeec33..1ff2a978c5a4 100644 --- a/packages/path_provider/path_provider_platform_interface/lib/path_provider_platform_interface.dart +++ b/packages/path_provider/path_provider_platform_interface/lib/path_provider_platform_interface.dart @@ -40,26 +40,26 @@ abstract class PathProviderPlatform extends PlatformInterface { /// Path to the temporary directory on the device that is not backed up and is /// suitable for storing caches of downloaded files. - Future getTemporaryPath() { + Future getTemporaryPath() { throw UnimplementedError('getTemporaryPath() has not been implemented.'); } /// Path to a directory where the application may place application support /// files. - Future getApplicationSupportPath() { + 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() { + 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() { + Future getApplicationDocumentsPath() { throw UnimplementedError( 'getApplicationDocumentsPath() has not been implemented.'); } @@ -67,7 +67,7 @@ abstract class PathProviderPlatform extends PlatformInterface { /// 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() { + Future getExternalStoragePath() { throw UnimplementedError( 'getExternalStoragePath() has not been implemented.'); } @@ -76,7 +76,7 @@ abstract class PathProviderPlatform extends PlatformInterface { /// stored. These paths typically reside on external storage like separate /// partitions or SD cards. Phones may have multiple storage directories /// available. - Future> getExternalCachePaths() { + Future?> getExternalCachePaths() { throw UnimplementedError( 'getExternalCachePaths() has not been implemented.'); } @@ -84,10 +84,10 @@ abstract class PathProviderPlatform extends PlatformInterface { /// 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({ + Future?> getExternalStoragePaths({ /// Optional parameter. See [StorageDirectory] for more informations on /// how this type translates to Android storage directories. - StorageDirectory type, + StorageDirectory? type, }) { throw UnimplementedError( 'getExternalStoragePaths() has not been implemented.'); @@ -95,7 +95,7 @@ abstract class PathProviderPlatform extends PlatformInterface { /// Path to the directory where downloaded files can be stored. /// This is typically only relevant on desktop operating systems. - Future getDownloadsPath() { + Future getDownloadsPath() { throw UnimplementedError('getDownloadsPath() has not been implemented.'); } } diff --git a/packages/path_provider/path_provider_platform_interface/lib/src/method_channel_path_provider.dart b/packages/path_provider/path_provider_platform_interface/lib/src/method_channel_path_provider.dart index 7826fa4365be..728c1068f876 100644 --- a/packages/path_provider/path_provider_platform_interface/lib/src/method_channel_path_provider.dart +++ b/packages/path_provider/path_provider_platform_interface/lib/src/method_channel_path_provider.dart @@ -30,34 +30,34 @@ class MethodChannelPathProvider extends PathProviderPlatform { _platform = platform; } - Future getTemporaryPath() { + Future getTemporaryPath() { return methodChannel.invokeMethod('getTemporaryDirectory'); } - Future getApplicationSupportPath() { + Future getApplicationSupportPath() { return methodChannel.invokeMethod('getApplicationSupportDirectory'); } - Future getLibraryPath() { + Future getLibraryPath() { if (!_platform.isIOS && !_platform.isMacOS) { throw UnsupportedError('Functionality only available on iOS/macOS'); } return methodChannel.invokeMethod('getLibraryDirectory'); } - Future getApplicationDocumentsPath() { + Future getApplicationDocumentsPath() { return methodChannel .invokeMethod('getApplicationDocumentsDirectory'); } - Future getExternalStoragePath() { + Future getExternalStoragePath() { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); } return methodChannel.invokeMethod('getStorageDirectory'); } - Future> getExternalCachePaths() { + Future?> getExternalCachePaths() { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); } @@ -65,8 +65,8 @@ class MethodChannelPathProvider extends PathProviderPlatform { .invokeListMethod('getExternalCacheDirectories'); } - Future> getExternalStoragePaths({ - StorageDirectory type, + Future?> getExternalStoragePaths({ + StorageDirectory? type, }) async { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); @@ -77,7 +77,7 @@ class MethodChannelPathProvider extends PathProviderPlatform { ); } - Future getDownloadsPath() { + Future getDownloadsPath() { if (!_platform.isMacOS) { throw UnsupportedError('Functionality only available on macOS'); } diff --git a/packages/path_provider/path_provider_platform_interface/pubspec.yaml b/packages/path_provider/path_provider_platform_interface/pubspec.yaml index 36b539ba1077..946d2ed4b4fd 100644 --- a/packages/path_provider/path_provider_platform_interface/pubspec.yaml +++ b/packages/path_provider/path_provider_platform_interface/pubspec.yaml @@ -3,20 +3,20 @@ description: A common platform interface for the path_provider plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_platform_interface # 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.4 +version: 2.0.0-nullsafety dependencies: flutter: sdk: flutter - meta: ^1.0.5 - platform: ">=2.0.0 <4.0.0" - plugin_platform_interface: ^1.0.1 + meta: ^1.3.0-nullsafety.3 + platform: ^3.0.0-nullsafety.4 + plugin_platform_interface: ^1.1.0-nullsafety dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.10.0" diff --git a/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart b/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart index 99c9349f9ae5..7130d7743e69 100644 --- a/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart +++ b/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart @@ -19,7 +19,7 @@ void main() { const String kDownloadsPath = 'downloadsPath'; group('$MethodChannelPathProvider', () { - MethodChannelPathProvider methodChannelPathProvider; + late MethodChannelPathProvider methodChannelPathProvider; final List log = []; setUp(() async { @@ -59,7 +59,7 @@ void main() { }); test('getTemporaryPath', () async { - final String path = await methodChannelPathProvider.getTemporaryPath(); + final String? path = await methodChannelPathProvider.getTemporaryPath(); expect( log, [isMethodCall('getTemporaryDirectory', arguments: null)], @@ -68,7 +68,7 @@ void main() { }); test('getApplicationSupportPath', () async { - final String path = + final String? path = await methodChannelPathProvider.getApplicationSupportPath(); expect( log, @@ -92,7 +92,7 @@ void main() { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); - final String path = await methodChannelPathProvider.getLibraryPath(); + final String? path = await methodChannelPathProvider.getLibraryPath(); expect( log, [isMethodCall('getLibraryDirectory', arguments: null)], @@ -104,7 +104,7 @@ void main() { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'macos')); - final String path = await methodChannelPathProvider.getLibraryPath(); + final String? path = await methodChannelPathProvider.getLibraryPath(); expect( log, [isMethodCall('getLibraryDirectory', arguments: null)], @@ -113,7 +113,7 @@ void main() { }); test('getApplicationDocumentsPath', () async { - final String path = + final String? path = await methodChannelPathProvider.getApplicationDocumentsPath(); expect( log, @@ -125,13 +125,13 @@ void main() { }); test('getExternalCachePaths android succeeds', () async { - final List result = + final List? result = await methodChannelPathProvider.getExternalCachePaths(); expect( log, [isMethodCall('getExternalCacheDirectories', arguments: null)], ); - expect(result.length, 1); + expect(result!.length, 1); expect(result.first, kExternalCachePaths); }); @@ -147,10 +147,12 @@ void main() { } }); - for (StorageDirectory type - in StorageDirectory.values + [null]) { + for (StorageDirectory? type in [ + null, + ...StorageDirectory.values + ]) { test('getExternalStoragePaths (type: $type) android succeeds', () async { - final List result = + final List? result = await methodChannelPathProvider.getExternalStoragePaths(type: type); expect( log, @@ -162,7 +164,7 @@ void main() { ], ); - expect(result.length, 1); + expect(result!.length, 1); expect(result.first, kExternalStoragePaths); }); @@ -182,7 +184,7 @@ void main() { test('getDownloadsPath macos succeeds', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'macos')); - final String result = await methodChannelPathProvider.getDownloadsPath(); + final String? result = await methodChannelPathProvider.getDownloadsPath(); expect( log, [isMethodCall('getDownloadsDirectory', arguments: null)], diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md index bdb0ae5b2d9f..ef1f5043a2b7 100644 --- a/packages/path_provider/path_provider_windows/CHANGELOG.md +++ b/packages/path_provider/path_provider_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.4+4 + +* Update Flutter SDK constraint. + ## 0.0.4+3 * Remove unused `test` dependency. diff --git a/packages/path_provider/path_provider_windows/pubspec.yaml b/packages/path_provider/path_provider_windows/pubspec.yaml index 342774680dc4..62185f42e765 100644 --- a/packages/path_provider/path_provider_windows/pubspec.yaml +++ b/packages/path_provider/path_provider_windows/pubspec.yaml @@ -1,7 +1,7 @@ name: path_provider_windows description: Windows implementation of the path_provider plugin homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_windows -version: 0.0.4+3 +version: 0.0.4+4 flutter: plugin: @@ -26,4 +26,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md index 01b5ff7d1252..7df1834966dd 100644 --- a/packages/plugin_platform_interface/CHANGELOG.md +++ b/packages/plugin_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.1.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 1.1.0-nullsafety + +* Migrate to null safety. + ## 1.0.3 * Fix homepage in `pubspec.yaml`. diff --git a/packages/plugin_platform_interface/lib/plugin_platform_interface.dart b/packages/plugin_platform_interface/lib/plugin_platform_interface.dart index be4871928686..cd87b04dc739 100644 --- a/packages/plugin_platform_interface/lib/plugin_platform_interface.dart +++ b/packages/plugin_platform_interface/lib/plugin_platform_interface.dart @@ -41,7 +41,7 @@ import 'package:meta/meta.dart'; /// [MockPlatformInterfaceMixin] for a sample of using Mockito to mock a platform interface. abstract class PlatformInterface { /// Pass a private, class-specific `const Object()` as the `token`. - PlatformInterface({@required Object token}) : _instanceToken = token; + PlatformInterface({required Object token}) : _instanceToken = token; final Object _instanceToken; diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml index ae11b144ca8c..05fc918bf9b5 100644 --- a/packages/plugin_platform_interface/pubspec.yaml +++ b/packages/plugin_platform_interface/pubspec.yaml @@ -12,17 +12,17 @@ description: Reusable base class for Flutter plugin platform interfaces. # be done when absolutely necessary and after the ecosystem has already migrated to 1.X.Y version # that is forward compatible with 2.0.0 (ideally the ecosystem have migrated to depend on: # `plugin_platform_interface: >=1.X.Y <3.0.0`). -version: 1.0.3 +version: 1.1.0-nullsafety.1 repository: https://github.com/flutter/plugins/tree/master/packages/plugin_platform_interface environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" dependencies: - meta: ^1.0.0 + meta: ^1.3.0-nullsafety.3 dev_dependencies: mockito: ^4.1.1 - test: ^1.9.4 - pedantic: ^1.8.0 + test: ^1.10.0-nullsafety.1 + pedantic: ^1.10.0-nullsafety.1 diff --git a/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart b/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart index 0488c20f3efb..b07dd4dcede1 100644 --- a/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart +++ b/packages/plugin_platform_interface/test/plugin_platform_interface_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(egarciad): Remove once Mockito is migrated to null safety. +// @dart = 2.9 import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; diff --git a/packages/quick_actions/CHANGELOG.md b/packages/quick_actions/CHANGELOG.md index 0742d73f500a..a0c1b1f43c66 100644 --- a/packages/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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. diff --git a/packages/quick_actions/README.md b/packages/quick_actions/README.md index 21e7cfb619cb..4573523a5e36 100644 --- a/packages/quick_actions/README.md +++ b/packages/quick_actions/README.md @@ -43,6 +43,6 @@ quick action. ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). -For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). +For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin). diff --git a/packages/quick_actions/pubspec.yaml b/packages/quick_actions/pubspec.yaml index 187c5707ddea..e76cd9ce8bd2 100644 --- a/packages/quick_actions/pubspec.yaml +++ b/packages/quick_actions/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. homepage: https://github.com/flutter/plugins/tree/master/packages/quick_actions -version: 0.4.0+10 +version: 0.4.0+12 flutter: plugin: @@ -29,4 +29,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/sensors/CHANGELOG.md b/packages/sensors/CHANGELOG.md index a2693dd7ed0b..8970f1e76e5d 100644 --- a/packages/sensors/CHANGELOG.md +++ b/packages/sensors/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.4.2+8 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 0.4.2+7 + +* Update Flutter SDK constraint. + ## 0.4.2+6 * Update android compileSdkVersion to 29. diff --git a/packages/sensors/README.md b/packages/sensors/README.md index e3c80b2b2947..08a9b2ea2b8c 100644 --- a/packages/sensors/README.md +++ b/packages/sensors/README.md @@ -13,7 +13,7 @@ A Flutter plugin to access the accelerometer and gyroscope sensors. ## Usage To use this plugin, add `sensors` as a [dependency in your pubspec.yaml -file](https://flutter.io/platform-plugins/). +file](https://flutter.dev/docs/development/platform-integration/platform-channels). This will expose three classes of sensor events, through three different streams. diff --git a/packages/sensors/pubspec.yaml b/packages/sensors/pubspec.yaml index 4e830d9d0480..35feb4b1ed56 100644 --- a/packages/sensors/pubspec.yaml +++ b/packages/sensors/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/sensors # 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.4.2+6 +version: 0.4.2+8 flutter: plugin: @@ -31,4 +31,4 @@ dev_dependencies: environment: sdk: ">=2.1.0<3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/share/CHANGELOG.md b/packages/share/CHANGELOG.md index 883cd340ecfb..eef22bfcc76e 100644 --- a/packages/share/CHANGELOG.md +++ b/packages/share/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.0.0-nullsafety.1 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 0.6.5+5 + +* Update Flutter SDK constraint. + ## 0.6.5+4 * Fix iPad share window not showing when `origin` is null. diff --git a/packages/share/README.md b/packages/share/README.md index 750fca6a5b18..6ca38b416f03 100644 --- a/packages/share/README.md +++ b/packages/share/README.md @@ -1,6 +1,6 @@ # Share plugin -[![pub package](https://img.shields.io/pub/v/share.svg)](https://pub.dartlang.org/packages/share) +[![pub package](https://img.shields.io/pub/v/share.svg)](https://pub.dev/packages/share) A Flutter plugin to share content from your Flutter app via the platform's share dialog. @@ -17,7 +17,7 @@ For more details see: https://github.com/flutter/flutter/wiki/Package-migration- ## Usage -To use this plugin, add `share` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +To use this plugin, add `share` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ## Example diff --git a/packages/share/example/ios/Runner.xcodeproj/project.pbxproj b/packages/share/example/ios/Runner.xcodeproj/project.pbxproj index 639666b2865c..d03ef3e65776 100644 --- a/packages/share/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/share/example/ios/Runner.xcodeproj/project.pbxproj @@ -197,7 +197,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 12A149CFB1B2610A83692801 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -268,24 +267,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 12A149CFB1B2610A83692801 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", - ); - 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; diff --git a/packages/share/lib/share.dart b/packages/share/lib/share.dart index 4a3ff6f1de09..f15566714857 100644 --- a/packages/share/lib/share.dart +++ b/packages/share/lib/share.dart @@ -33,8 +33,8 @@ class Share { /// from [MethodChannel]. static Future share( String text, { - String subject, - Rect sharePositionOrigin, + String? subject, + Rect? sharePositionOrigin, }) { assert(text != null); assert(text.isNotEmpty); @@ -67,10 +67,10 @@ class Share { /// from [MethodChannel]. static Future shareFiles( List paths, { - List mimeTypes, - String subject, - String text, - Rect sharePositionOrigin, + List? mimeTypes, + String? subject, + String? text, + Rect? sharePositionOrigin, }) { assert(paths != null); assert(paths.isNotEmpty); diff --git a/packages/share/pubspec.yaml b/packages/share/pubspec.yaml index 5bd79effe4a7..07ead8f3f659 100644 --- a/packages/share/pubspec.yaml +++ b/packages/share/pubspec.yaml @@ -2,10 +2,7 @@ name: share description: Flutter plugin for sharing content via the platform share UI, using the ACTION_SEND intent on Android and UIActivityViewController on iOS. homepage: https://github.com/flutter/plugins/tree/master/packages/share -# 0.6.y+z is compatible with 1.0.0, if you land a breaking change bump -# the version to 2.0.0. -# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.6.5+4 +version: 2.0.0-nullsafety.1 flutter: plugin: @@ -17,20 +14,20 @@ flutter: pluginClass: FLTSharePlugin dependencies: - meta: ^1.0.5 - mime: ^0.9.7 + meta: ^1.3.0-nullsafety.6 + mime: ^1.0.0-nullsafety.0 flutter: sdk: flutter dev_dependencies: - test: ^1.3.0 - mockito: ^3.0.0 + test: ^1.16.0-nullsafety.13 + mockito: ^4.1.3 flutter_test: sdk: flutter integration_test: path: ../integration_test - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.3 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" + sdk: ">=2.12.0-0 <3.0.0" diff --git a/packages/share/test/share_test.dart b/packages/share/test/share_test.dart index e862d1baf579..fa9f980beae3 100644 --- a/packages/share/test/share_test.dart +++ b/packages/share/test/share_test.dart @@ -15,7 +15,7 @@ import 'package:flutter/services.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - MockMethodChannel mockChannel; + late MockMethodChannel mockChannel; setUp(() { mockChannel = MockMethodChannel(); @@ -26,14 +26,6 @@ void main() { }); }); - test('sharing null fails', () { - expect( - () => Share.share(null), - throwsA(const TypeMatcher()), - ); - verifyZeroInteractions(mockChannel); - }); - test('sharing empty fails', () { expect( () => Share.share(''), @@ -58,14 +50,6 @@ void main() { })); }); - test('sharing null file fails', () { - expect( - () => Share.shareFiles([null]), - throwsA(const TypeMatcher()), - ); - verifyZeroInteractions(mockChannel); - }); - test('sharing empty file fails', () { expect( () => Share.shareFiles(['']), diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index d86588b33098..5e09b3f87fb0 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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`. diff --git a/packages/shared_preferences/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md index 9ccac0ac49ae..516d7a91b848 100644 --- a/packages/shared_preferences/shared_preferences/README.md +++ b/packages/shared_preferences/shared_preferences/README.md @@ -1,6 +1,6 @@ # Shared preferences plugin -[![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dartlang.org/packages/shared_preferences) +[![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, @@ -16,7 +16,7 @@ Please use `shared_preferences: '>=0.5.y+x <2.0.0'` as your dependency constrain For more details see: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 ## Usage -To use this plugin, add `shared_preferences` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +To use this plugin, add `shared_preferences` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### Example diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 8897ee7c78fe..69689c738b8e 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/shared_prefere # 0.5.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.5.13 +version: 0.5.13+2 flutter: plugin: @@ -48,4 +48,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart index 763d32642cfa..80faba404154 100755 --- a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 +// @dart = 2.9 import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index 6a0a0414086b..9821b79c5cc2 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.3+1 + +* Update Flutter SDK constraint. + ## 0.0.3 * Update integration test examples to use `testWidgets` instead of `test`. diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index 2548ca1f7965..50709aac5f8a 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -1,6 +1,6 @@ name: shared_preferences_linux description: Linux implementation of the shared_preferences plugin -version: 0.0.3 +version: 0.0.3+1 homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_linux flutter: @@ -12,7 +12,7 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.8" dependencies: file: ">=5.1.0 <7.0.0" diff --git a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md b/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md index 177f1f2e02e2..3eff6db6949e 100644 --- a/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+12 + +* Update Flutter SDK constraint. + ## 0.0.1+11 * Remove unused `test` dependency. diff --git a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml b/packages/shared_preferences/shared_preferences_macos/pubspec.yaml index b161327e3f3d..afc5b9ec0b4e 100644 --- a/packages/shared_preferences/shared_preferences_macos/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_macos/pubspec.yaml @@ -3,7 +3,7 @@ description: macOS implementation of the shared_preferences plugin. # 0.0.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.0.1+11 +version: 0.0.1+12 homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_macos flutter: @@ -14,7 +14,7 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.8" dependencies: shared_preferences_platform_interface: ^1.0.0 diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md index 5fe7b18160ba..88d3a9ac5f00 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.5 + +* Update Flutter SDK constraint. + ## 1.0.4 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml index 3b8f2bfcada4..da31497df1c6 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml @@ -1,7 +1,7 @@ name: shared_preferences_platform_interface description: A common platform interface for the shared_preferences plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_platform_interface -version: 1.0.4 +version: 1.0.5 dependencies: meta: ^1.0.4 @@ -15,4 +15,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.8" diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md index b2076dd8c17e..2ba877856da6 100644 --- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2+8 + +* Update Flutter SDK constraint. + ## 0.1.2+7 * Removed Android folder from `shared_preferences_web`. diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml index a153135b6da7..d657b2300727 100644 --- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/shared_prefere # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.2+7 +version: 0.1.2+8 flutter: plugin: @@ -28,4 +28,4 @@ dev_dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index ee7b36884237..ecc790ef9bab 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.2+1 + +* Update Flutter SDK constraint. + ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index 32d4cb5c242c..ce559d5ea3fb 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -1,7 +1,7 @@ name: shared_preferences_windows description: Windows implementation of shared_preferences homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_windows -version: 0.0.2 +version: 0.0.2+1 flutter: plugin: @@ -12,7 +12,7 @@ flutter: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + flutter: ">=1.12.8" dependencies: shared_preferences_platform_interface: ^1.0.0 diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 72a0cc92e37a..9df2d27a2b57 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,23 @@ +## 6.0.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 6.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 6.0.0-nullsafety + +* Migrate to null safety. + +## 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`. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index 811dcd5b4ea1..573624fa18eb 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -1,6 +1,6 @@ # url_launcher -[![pub package](https://img.shields.io/pub/v/url_launcher.svg)](https://pub.dartlang.org/packages/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 in the mobile platform. Supports iOS, Android, web, Windows, macOS, and Linux. @@ -14,31 +14,28 @@ To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml fil import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -void main() { - runApp(Scaffold( - body: Center( - child: RaisedButton( - onPressed: _launchURL, - child: Text('Show Flutter homepage'), +const _url = 'https://flutter.dev'; + +void main() => runApp( + const MaterialApp( + home: Material( + child: Center( + child: RaisedButton( + onPressed: _launchURL, + child: Text('Show Flutter homepage'), + ), + ), + ), ), - ), - )); -} - -_launchURL() async { - const url = 'https://flutter.dev'; - if (await canLaunch(url)) { - await launch(url); - } else { - throw 'Could not launch $url'; - } -} + ); +void _launchURL() async => + await canLaunch(_url) ? await launch(_url) : throw 'Could not launch $_url'; ``` ## Supported URL schemes -The [`launch`](https://www.dartdocs.org/documentation/url_launcher/latest/url_launcher/launch.html) method +The [`launch`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launch.html) method takes a string argument containing a URL. This URL can be formatted using a number of different URL schemes. The supported URL schemes depend on the underlying platform and installed apps. @@ -83,7 +80,7 @@ launching a URL using the `sms` scheme, or a device may not have an email app and thus no support for launching a URL using the `email` scheme. We recommend checking which URL schemes are supported using the -[`canLaunch`](https://www.dartdocs.org/documentation/url_launcher/latest/url_launcher/canLaunch.html) +[`canLaunch`](https://pub.dev/documentation/url_launcher/latest/url_launcher/canLaunch.html) method prior to calling `launch`. If the `canLaunch` method returns false, as a best practice we suggest adjusting the application UI so that the unsupported URL is never triggered; for example, if the `email` scheme is not supported, a diff --git a/packages/url_launcher/url_launcher/example/integration_test/url_launcher_test.dart b/packages/url_launcher/url_launcher/example/integration_test/url_launcher_test.dart index 4c0f5031ee6b..80d21b740c1e 100644 --- a/packages/url_launcher/url_launcher/example/integration_test/url_launcher_test.dart +++ b/packages/url_launcher/url_launcher/example/integration_test/url_launcher_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(egarciad): Remove once integration_test is migrated to null safety. +// @dart = 2.9 + import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index f7d90c4bef65..b3e65f38a794 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -27,7 +27,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); + MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override @@ -35,7 +35,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - Future _launched; + Future? _launched; String _phone = ''; Future _launchInBrowser(String url) async { diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 94df1a4b4959..7caea27744db 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -12,13 +12,13 @@ dev_dependencies: path: ../../../integration_test flutter_driver: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 mockito: ^4.1.1 - plugin_platform_interface: ^1.0.0 + plugin_platform_interface: ^1.1.0-nullsafety.1 flutter: uses-material-design: true environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/url_launcher/url_launcher/example/test/url_launcher_example_test.dart b/packages/url_launcher/url_launcher/example/test/url_launcher_example_test.dart index 41b9f6f5ec6c..eddc126a8e66 100644 --- a/packages/url_launcher/url_launcher/example/test/url_launcher_example_test.dart +++ b/packages/url_launcher/url_launcher/example/test/url_launcher_example_test.dart @@ -1,3 +1,10 @@ +// Copyright 2017 The Chromium 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(egarciad): Remove once mockito is migrated to null safety. +// @dart = 2.9 + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/url_launcher/url_launcher/example/test_driver/integration_test.dart b/packages/url_launcher/url_launcher/example/test_driver/integration_test.dart index 7a2c21338786..e56756f38cbd 100644 --- a/packages/url_launcher/url_launcher/example/test_driver/integration_test.dart +++ b/packages/url_launcher/url_launcher/example/test_driver/integration_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(egarciad): Remove once flutter_driver is migrated to null safety. +// @dart = 2.9 + import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/packages/url_launcher/url_launcher/lib/src/link.dart b/packages/url_launcher/url_launcher/lib/src/link.dart index bd54789accfb..f859bc4ad2cf 100644 --- a/packages/url_launcher/url_launcher/lib/src/link.dart +++ b/packages/url_launcher/url_launcher/lib/src/link.dart @@ -40,7 +40,7 @@ class Link extends StatelessWidget implements LinkInfo { final LinkWidgetBuilder builder; /// The destination that this link leads to. - final Uri uri; + final Uri? uri; /// The target indicating where to open the link. final LinkTarget target; @@ -51,12 +51,11 @@ 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. Link({ - Key key, - @required this.uri, - LinkTarget target, - @required this.builder, - }) : target = target ?? LinkTarget.defaultTarget, - super(key: key); + Key? key, + required this.uri, + this.target = LinkTarget.defaultTarget, + required this.builder, + }) : super(key: key); LinkDelegate get _effectiveDelegate { return UrlLauncherPlatform.instance.linkDelegate ?? @@ -90,16 +89,17 @@ class DefaultLinkDelegate extends StatelessWidget { bool get _useWebView { if (link.target == LinkTarget.self) return true; if (link.target == LinkTarget.blank) return false; - return null; + return false; } Future _followLink(BuildContext context) async { - if (!link.uri.hasScheme) { + if (!link.uri!.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(); - return pushRouteNameToFramework(context, routeName); + await pushRouteNameToFramework(context, routeName); + return; } // At this point, we know that the link is external. So we use the `launch` @@ -119,7 +119,6 @@ class DefaultLinkDelegate extends StatelessWidget { context: ErrorDescription('during launching a link'), )); } - return Future.value(null); } @override diff --git a/packages/url_launcher/url_launcher/lib/url_launcher.dart b/packages/url_launcher/url_launcher/lib/url_launcher.dart index 25aa623a590f..6138fff2e3d9 100644 --- a/packages/url_launcher/url_launcher/lib/url_launcher.dart +++ b/packages/url_launcher/url_launcher/lib/url_launcher.dart @@ -62,16 +62,15 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. /// is set to true and the universal link failed to launch. Future launch( String urlString, { - bool forceSafariVC, - bool forceWebView, - bool enableJavaScript, - bool enableDomStorage, - bool universalLinksOnly, - Map headers, - Brightness statusBarBrightness, - String webOnlyWindowName, + bool forceSafariVC = true, + bool forceWebView = false, + bool enableJavaScript = false, + bool enableDomStorage = false, + bool universalLinksOnly = false, + Map headers = const {}, + Brightness? statusBarBrightness, + String? webOnlyWindowName, }) async { - assert(urlString != null); final Uri url = Uri.parse(urlString.trimLeft()); final bool isWebURL = url.scheme == 'http' || url.scheme == 'https'; if ((forceSafariVC == true || forceWebView == true) && !isWebURL) { @@ -84,29 +83,32 @@ Future launch( /// [true] so that ui is automatically computed if [statusBarBrightness] is set. bool previousAutomaticSystemUiAdjustment = true; if (statusBarBrightness != null && - defaultTargetPlatform == TargetPlatform.iOS) { + defaultTargetPlatform == TargetPlatform.iOS && + WidgetsBinding.instance != null) { previousAutomaticSystemUiAdjustment = - WidgetsBinding.instance.renderView.automaticSystemUiAdjustment; - WidgetsBinding.instance.renderView.automaticSystemUiAdjustment = false; + WidgetsBinding.instance!.renderView.automaticSystemUiAdjustment; + 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 ?? false, - enableJavaScript: enableJavaScript ?? false, - enableDomStorage: enableDomStorage ?? false, - universalLinksOnly: universalLinksOnly ?? false, - headers: headers ?? {}, + useSafariVC: forceSafariVC, + useWebView: forceWebView, + enableJavaScript: enableJavaScript, + enableDomStorage: enableDomStorage, + universalLinksOnly: universalLinksOnly, + headers: headers, webOnlyWindowName: webOnlyWindowName, ); - assert(previousAutomaticSystemUiAdjustment != null); - if (statusBarBrightness != null) { - WidgetsBinding.instance.renderView.automaticSystemUiAdjustment = + + if (statusBarBrightness != null && WidgetsBinding.instance != null) { + WidgetsBinding.instance!.renderView.automaticSystemUiAdjustment = previousAutomaticSystemUiAdjustment; } + return result; } @@ -118,9 +120,6 @@ Future launch( /// For more information see the [Managing package visibility](https://developer.android.com/training/basics/intents/package-visibility) /// article in the Android docs. Future canLaunch(String urlString) async { - if (urlString == null) { - return false; - } return await UrlLauncherPlatform.instance.canLaunch(urlString); } diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 4d5e3a7cb0bd..e2d7a161e1ea 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.7.11 +version: 6.0.0-nullsafety.2 flutter: plugin: @@ -12,8 +12,9 @@ flutter: pluginClass: UrlLauncherPlugin ios: pluginClass: FLTURLLauncherPlugin - web: - default_package: url_launcher_web + # TODO(mvanbeusekom): Temporary disabled until web is migrated to nnbd (advised by @blasten). + #web: + # default_package: url_launcher_web linux: default_package: url_laucher_linux macos: @@ -24,25 +25,26 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_platform_interface: ^1.0.9 + url_launcher_platform_interface: ^2.0.0-nullsafety # The design on https://flutter.dev/go/federated-plugins was to leave # this constraint as "any". We cannot do it right now as it fails pub publish # validation, so we set a ^ constraint. # TODO(amirh): Revisit this (either update this part in the design or the pub tool). # https://github.com/flutter/flutter/issues/46264 - url_launcher_web: ^0.1.5 - url_launcher_linux: ^0.0.1 - url_launcher_macos: ^0.0.1 - url_launcher_windows: ^0.0.1 + url_launcher_linux: ^0.1.0-nullsafety + url_launcher_macos: ^0.1.0-nullsafety + url_launcher_windows: ^0.1.0-nullsafety + # TODO(mvanbeusekom): Temporary disabled until web is migrated to nnbd (advised by @blasten). + #url_launcher_web: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter - test: ^1.3.0 + test: ^1.10.0-nullsafety.1 mockito: ^4.1.1 - plugin_platform_interface: ^1.0.0 - pedantic: ^1.8.0 + plugin_platform_interface: ^1.1.0-nullsafety.1 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index d525153dc0a0..46903aadaede 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 +// TODO(egarciad): Remove once Mockito has been migrated to null safety. +// @dart = 2.9 import 'dart:ui'; import 'package:flutter/material.dart'; diff --git a/packages/url_launcher/url_launcher/test/url_launcher_test.dart b/packages/url_launcher/url_launcher/test/url_launcher_test.dart index f18e16c7e318..89a7801e1ca8 100644 --- a/packages/url_launcher/url_launcher/test/url_launcher_test.dart +++ b/packages/url_launcher/url_launcher/test/url_launcher_test.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 +// TODO(mvanbeusekom): Remove once Mockito is migrated to null safety. +// @dart = 2.9 import 'dart:async'; import 'dart:ui'; + import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:flutter/foundation.dart'; @@ -41,10 +43,6 @@ void main() { }); }); group('launch', () { - test('requires a non-null urlString', () { - expect(() => launch(null), throwsAssertionError); - }); - test('default behavior', () async { await launch('http://flutter.dev/'); expect( diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 1b4041fe4c73..cc3ee456c938 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.1.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 0.1.0-nullsafety.1 + +* Migrate to null safety. + +## 0.0.2+1 + +* Update Flutter SDK constraint. + ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. diff --git a/packages/url_launcher/url_launcher_linux/example/README.md b/packages/url_launcher/url_launcher_linux/example/README.md index 28dd90d71700..c200da8974d1 100644 --- a/packages/url_launcher/url_launcher_linux/example/README.md +++ b/packages/url_launcher/url_launcher_linux/example/README.md @@ -5,4 +5,4 @@ Demonstrates how to use the url_launcher plugin. ## Getting Started For help getting started with Flutter, view our online -[documentation](http://flutter.io/). +[documentation](https://flutter.dev/). diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index d231bf98476f..41366a1d4f1e 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -1,6 +1,6 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. -version: 0.0.2 +version: 0.1.0-nullsafety.2 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux flutter: @@ -10,8 +10,8 @@ flutter: pluginClass: UrlLauncherPlugin environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.8" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index 9462960ad45e..8a0e6575b5f2 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,3 +1,15 @@ +# 0.1.0-nullsafety.1 + +* Bump SDK to support null safety. + +# 0.1.0-nullsafety + +* Migrate to null safety. + +## 0.0.2+1 + +* Update Flutter SDK constraint. + ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 1a14e6bc9bb9..9ce9c9c47ea9 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -3,7 +3,7 @@ description: macOS implementation of the url_launcher plugin. # 0.0.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.0.2 +version: 0.1.0-nullsafety.1 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_macos flutter: @@ -14,8 +14,8 @@ flutter: fileName: url_launcher_macos.dart environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.8" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index 10057e147adf..5bbbe9d28cd1 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 1.0.10 + +* Update Flutter SDK constraint. + ## 1.0.9 * Laid the groundwork for introducing a Link widget. diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart index 425dc886d29f..a176972e06bd 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart @@ -16,7 +16,7 @@ typedef FollowLink = Future Function(); /// the widget tree under it. typedef LinkWidgetBuilder = Widget Function( BuildContext context, - FollowLink followLink, + FollowLink? followLink, ); /// Signature for a delegate function to build the [Link] widget. @@ -31,7 +31,7 @@ final MethodCodec _codec = const JSONMethodCodec(); class LinkTarget { /// Const private constructor with a [debugLabel] to allow the creation of /// multiple distinct const instances. - const LinkTarget._({this.debugLabel}); + const LinkTarget._({required this.debugLabel}); /// Used to distinguish multiple const instances of [LinkTarget]. final String debugLabel; @@ -64,7 +64,7 @@ abstract class LinkInfo { LinkWidgetBuilder get builder; /// The destination that this link leads to. - Uri get uri; + Uri? get uri; /// The target indicating where to open the link. LinkTarget get target; @@ -80,10 +80,14 @@ Future pushRouteNameToFramework( String routeName, { @visibleForTesting bool debugForceRouter = false, }) { + final PlatformMessageCallback? onPlatformMessage = window.onPlatformMessage; + if (onPlatformMessage == null) { + return Future.value(null); + } final Completer completer = Completer(); if (debugForceRouter || _hasRouter(context)) { SystemNavigator.routeInformationUpdated(location: routeName); - window.onPlatformMessage( + onPlatformMessage( 'flutter/navigation', _codec.encodeMethodCall( MethodCall('pushRouteInformation', { @@ -94,7 +98,7 @@ Future pushRouteNameToFramework( completer.complete, ); } else { - window.onPlatformMessage( + onPlatformMessage( 'flutter/navigation', _codec.encodeMethodCall(MethodCall('pushRoute', routeName)), completer.complete, diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart b/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart index ac5bfa230289..7b9dfc9cc5cf 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import 'package:meta/meta.dart' show required; import 'link.dart'; import 'url_launcher_platform_interface.dart'; @@ -15,14 +14,14 @@ 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; + final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, - ); + ).then((value) => value ?? false); } @override @@ -33,13 +32,13 @@ class MethodChannelUrlLauncher extends UrlLauncherPlatform { @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, + required bool useSafariVC, + required bool useWebView, + required bool enableJavaScript, + required bool enableDomStorage, + required bool universalLinksOnly, + required Map headers, + String? webOnlyWindowName, }) { return _channel.invokeMethod( 'launch', @@ -52,6 +51,6 @@ class MethodChannelUrlLauncher extends UrlLauncherPlatform { 'universalLinksOnly': universalLinksOnly, 'headers': headers, }, - ); + ).then((value) => value ?? false); } } diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart index 75002ff9eb4d..2a4edfa8d1af 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart @@ -4,7 +4,6 @@ import 'dart:async'; -import 'package:meta/meta.dart' show required; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:url_launcher_platform_interface/link.dart'; @@ -40,7 +39,7 @@ abstract class UrlLauncherPlatform extends PlatformInterface { } /// The delegate used by the Link widget to build itself. - LinkDelegate get linkDelegate; + LinkDelegate? get linkDelegate; /// Returns `true` if this platform is able to launch [url]. Future canLaunch(String url) { @@ -53,13 +52,13 @@ abstract class UrlLauncherPlatform extends PlatformInterface { /// 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, + 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.'); } diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index ce0fdd936c9a..e576e967ec46 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -3,20 +3,19 @@ description: A common platform interface for the url_launcher plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface # 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.9 +version: 2.0.0-nullsafety.1 dependencies: flutter: sdk: flutter - meta: ^1.0.5 - plugin_platform_interface: ^1.0.1 + plugin_platform_interface: ^1.1.0-nullsafety.1 dev_dependencies: flutter_test: sdk: flutter mockito: ^4.1.1 - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.22.0" diff --git a/packages/url_launcher/url_launcher_platform_interface/test/link_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/link_test.dart index 99a885ccc179..58cdd22dca02 100644 --- a/packages/url_launcher/url_launcher_platform_interface/test/link_test.dart +++ b/packages/url_launcher/url_launcher_platform_interface/test/link_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(egarciad): Remove once Mockito has been migrated to null safety. +// @dart = 2.9 + import 'dart:ui'; import 'package:mockito/mockito.dart'; diff --git a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart index d88f53ad58d0..dfd4b7380c3e 100644 --- a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart +++ b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(mvanbeusekom): Remove once Mockito is migrated to null safety. +// @dart = 2.9 import 'package:mockito/mockito.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -42,6 +44,10 @@ void main() { final List log = []; channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); + + // Return null explicitly instead of relying on the implicit null + // returned by the method channel if no return statement is specified. + return null; }); final MethodChannelUrlLauncher launcher = MethodChannelUrlLauncher(); @@ -62,6 +68,12 @@ void main() { ); }); + test('canLaunch should return false if platform returns null', () async { + final canLaunch = await launcher.canLaunch('http://example.com/'); + + expect(canLaunch, false); + }); + test('launch', () async { await launcher.launch( 'http://example.com/', @@ -270,6 +282,20 @@ void main() { ); }); + test('launch should return false if platform returns null', () async { + final 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( diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 093029104f8f..c8d52f5df13f 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +# 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). @@ -37,7 +41,7 @@ # 0.1.2 -- Adds "tel" and "sms" support +- Adds "tel" and "sms" support # 0.1.1+6 diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index b40a8ea236cc..2d1b8af8e49f 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/u # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.5+1 +version: 0.1.5+2 flutter: plugin: @@ -15,6 +15,12 @@ flutter: dependencies: url_launcher_platform_interface: ^1.0.9 + # TODO(mvanbeusekom): Update to use pub.dev once null safety version is published. + # url_launcher_platform_interface: + # git: + # url: https://github.com/flutter/plugins.git + # ref: nnbd + # path: packages/url_launcher/url_launcher_platform_interface flutter: sdk: flutter flutter_web_plugins: @@ -25,6 +31,9 @@ dev_dependencies: flutter_test: sdk: flutter url_launcher: ^5.2.5 + # TODO(mvanbeusekom): Update to use pub.dev once null safety version is published. + # url_launcher: + # path: ../url_launcher pedantic: ^1.8.0 mockito: ^4.1.1 integration_test: @@ -32,4 +41,4 @@ dev_dependencies: environment: sdk: ">=2.2.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.10.0" diff --git a/packages/url_launcher/url_launcher_web/test/pubspec.yaml b/packages/url_launcher/url_launcher_web/test/pubspec.yaml index e755dff85004..b8c541f72986 100644 --- a/packages/url_launcher/url_launcher_web/test/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/test/pubspec.yaml @@ -2,7 +2,7 @@ name: regular_integration_tests publish_to: none environment: - sdk: ">=2.2.2 <3.0.0" + sdk: ">=2.10.0-56.0.dev <3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index ca4718813608..e9649ff6fd1e 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.1.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 0.1.0-nullsafety + +* Migrate to null-safety. + +## 0.0.2+1 + +* Update Flutter SDK constraint. + ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index f543e7aea2fd..d2da4c534322 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -3,7 +3,7 @@ description: Windows implementation of the url_launcher plugin. # 0.0.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.0.2 +version: 0.1.0-nullsafety.1 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_windows flutter: @@ -13,8 +13,8 @@ flutter: pluginClass: UrlLauncherPlugin environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.8" dependencies: flutter: diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 90082d8358e3..4e2826262eaf 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,31 @@ +## 2.0.0-nullsafety.5 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety.4 + +* Fixed an issue where `isBuffering` was not updating on Android. + +## 2.0.0-nullsafety.3 + +* Dart null safety requires `2.12`. + +## 2.0.0-nullsafety.2 + +* Bump SDK version. + +## 2.0.0-nullsafety.1 + +* Merge master. + +## 2.0.0-nullsafety + +* Migration to null safety. + +## 1.0.2 + +* Update Flutter SDK constraint. + ## 1.0.1 * Android: Dispose video players when app is closed. diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index 5eff770b87a2..e64ce152f85b 100644 --- a/packages/video_player/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -1,6 +1,6 @@ # Video Player plugin for Flutter -[![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dartlang.org/packages/video_player) +[![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. @@ -12,7 +12,7 @@ A Flutter plugin for iOS, Android and Web for playing back video on a Widget sur ## Installation -First, add `video_player` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). +First, add `video_player` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ### iOS diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java index 78da7150edf0..98cf6dbaacea 100644 --- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java +++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.7), do not edit directly. +// Autogenerated from Pigeon (v0.1.12), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.videoplayer; @@ -597,7 +597,7 @@ static void setup(BinaryMessenger binaryMessenger, VideoPlayerApi api) { private static HashMap wrapError(Exception exception) { HashMap errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); - errorMap.put("code", null); + errorMap.put("code", exception.getClass().getSimpleName()); errorMap.put("details", null); return errorMap; } diff --git a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 33c2f42afe1e..65657509b49f 100644 --- a/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -169,10 +169,21 @@ public void onCancel(Object o) { exoPlayer.addListener( new EventListener() { + 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) { @@ -184,10 +195,15 @@ public void onPlaybackStateChanged(final int playbackState) { event.put("event", "completed"); eventSink.success(event); } + + if (playbackState != Player.STATE_BUFFERING) { + setBuffering(false); + } } @Override public void onPlayerError(final ExoPlaybackException error) { + setBuffering(false); if (eventSink != null) { eventSink.error("VideoError", "Video player had error " + error, null); } diff --git a/packages/video_player/video_player/example/integration_test/video_player_test.dart b/packages/video_player/video_player/example/integration_test/video_player_test.dart index 639cca9b8631..9e273e02dc4d 100644 --- a/packages/video_player/video_player/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player/example/integration_test/video_player_test.dart @@ -2,6 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(amirh): Remove this once flutter_driver supports null safety. +// https://github.com/flutter/flutter/issues/71379 +// @dart = 2.9 +import 'dart:async'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -22,17 +28,65 @@ void main() { testWidgets('can be initialized', (WidgetTester tester) async { await _controller.initialize(); - expect(_controller.value.initialized, true); + expect(_controller.value.isInitialized, true); expect(_controller.value.position, const Duration(seconds: 0)); expect(_controller.value.isPlaying, false); expect(_controller.value.duration, const Duration(seconds: 7, milliseconds: 540)); }); + testWidgets( + 'reports buffering status', + (WidgetTester tester) async { + VideoPlayerController networkController = VideoPlayerController.network( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + ); + await networkController.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await networkController.setVolume(0); + final Completer started = Completer(); + final Completer ended = Completer(); + bool startedBuffering = false; + bool endedBuffering = false; + networkController.addListener(() { + if (networkController.value.isBuffering && !startedBuffering) { + startedBuffering = true; + started.complete(); + } + if (startedBuffering && + !networkController.value.isBuffering && + !endedBuffering) { + endedBuffering = true; + ended.complete(); + } + }); + + await networkController.play(); + await networkController.seekTo(const Duration(seconds: 5)); + await tester.pumpAndSettle(_playDuration); + await networkController.pause(); + + expect(networkController.value.isPlaying, false); + expect(networkController.value.position, + (Duration position) => position > const Duration(seconds: 0)); + + await started; + expect(startedBuffering, true); + + await ended; + expect(endedBuffering, true); + }, + skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), + ); + 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); @@ -58,6 +112,9 @@ void main() { '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(); @@ -104,6 +161,6 @@ void main() { await tester.pumpAndSettle(); expect(_controller.value.isPlaying, true); - }); + }, skip: kIsWeb); // Web does not support local assets. }); } diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index a99b9da6bd0c..42eaaa578fcf 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -108,7 +108,7 @@ class _ButterFlyAssetVideoInList extends StatelessWidget { /// A filler card to show the video in a list of scrolling contents. class _ExampleCard extends StatelessWidget { - const _ExampleCard({Key key, this.title}) : super(key: key); + const _ExampleCard({Key? key, required this.title}) : super(key: key); final String title; @@ -150,7 +150,7 @@ class _ButterFlyAssetVideo extends StatefulWidget { } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { - VideoPlayerController _controller; + late VideoPlayerController _controller; @override void initState() { @@ -206,7 +206,7 @@ class _BumbleBeeRemoteVideo extends StatefulWidget { } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { - VideoPlayerController _controller; + late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context) @@ -265,7 +265,8 @@ class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { } class _ControlsOverlay extends StatelessWidget { - const _ControlsOverlay({Key key, this.controller}) : super(key: key); + const _ControlsOverlay({Key? key, required this.controller}) + : super(key: key); static const _examplePlaybackRates = [ 0.25, @@ -345,7 +346,7 @@ class _PlayerVideoAndPopPage extends StatefulWidget { } class _PlayerVideoAndPopPageState extends State<_PlayerVideoAndPopPage> { - VideoPlayerController _videoPlayerController; + late VideoPlayerController _videoPlayerController; bool startedPlaying = false; @override diff --git a/packages/video_player/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml index dd8fd8d06bd8..fb18d8b75efa 100644 --- a/packages/video_player/video_player/example/pubspec.yaml +++ b/packages/video_player/video_player/example/pubspec.yaml @@ -16,7 +16,8 @@ dev_dependencies: sdk: flutter integration_test: path: ../../../integration_test - pedantic: ^1.8.0 + test: any + pedantic: ^1.10.0-nullsafety.1 flutter: uses-material-design: true @@ -26,5 +27,5 @@ flutter: - assets/bumble_bee_captions.srt environment: - sdk: ">=2.8.0 <3.0.0" + sdk: ">=2.12.0-0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/video_player/video_player/example/test_driver/integration_test.dart b/packages/video_player/video_player/example/test_driver/integration_test.dart index 7a2c21338786..7873abae2996 100644 --- a/packages/video_player/video_player/example/test_driver/integration_test.dart +++ b/packages/video_player/video_player/example/test_driver/integration_test.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// TODO(egarciad): Remove once Flutter driver is migrated to null safety. +// @dart = 2.9 + import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/packages/video_player/video_player/example/test_driver/video_player.dart b/packages/video_player/video_player/example/test_driver/video_player.dart new file mode 100644 index 000000000000..c1ced19e9b7e --- /dev/null +++ b/packages/video_player/video_player/example/test_driver/video_player.dart @@ -0,0 +1,14 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(egarciad): Remove once Flutter driver is migrated to null safety. +// @dart = 2.9 + +import 'package:flutter_driver/driver_extension.dart'; +import 'package:video_player_example/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/packages/video_player/video_player/example/test_driver/video_player_test.dart b/packages/video_player/video_player/example/test_driver/video_player_test.dart new file mode 100644 index 000000000000..fcbdbb274f7a --- /dev/null +++ b/packages/video_player/video_player/example/test_driver/video_player_test.dart @@ -0,0 +1,30 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(egarciad): Remove once Flutter driver is migrated to null safety. +// @dart = 2.9 + +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'); +} diff --git a/packages/video_player/video_player/ios/Classes/messages.h b/packages/video_player/video_player/ios/Classes/messages.h index 3c68b3dd24d4..84e8fc5e5cff 100644 --- a/packages/video_player/video_player/ios/Classes/messages.h +++ b/packages/video_player/video_player/ios/Classes/messages.h @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.7), do not edit directly. +// Autogenerated from Pigeon (v0.1.12), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; diff --git a/packages/video_player/video_player/ios/Classes/messages.m b/packages/video_player/video_player/ios/Classes/messages.m index e71f8b89254d..58ff7292d2b2 100644 --- a/packages/video_player/video_player/ios/Classes/messages.m +++ b/packages/video_player/video_player/ios/Classes/messages.m @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.7), do not edit directly. +// Autogenerated from Pigeon (v0.1.12), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.h" #import @@ -7,6 +7,7 @@ #error File requires ARC to be enabled. #endif +#ifndef __clang_analyzer__ static NSDictionary *wrapResult(NSDictionary *result, FlutterError *error) { NSDictionary *errorDict = (NSDictionary *)[NSNull null]; if (error) { @@ -59,9 +60,9 @@ + (FLTTextureMessage *)fromMap:(NSDictionary *)dict { return result; } - (NSDictionary *)toMap { - return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", nil]; + return + [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), + @"textureId", nil]; } @end @@ -112,10 +113,9 @@ + (FLTLoopingMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", - (self.isLooping != nil ? self.isLooping : [NSNull null]), - @"isLooping", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.isLooping ? self.isLooping : [NSNull null]), @"isLooping", + nil]; } @end @@ -134,9 +134,8 @@ + (FLTVolumeMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", (self.volume != nil ? self.volume : [NSNull null]), - @"volume", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.volume ? self.volume : [NSNull null]), @"volume", nil]; } @end @@ -155,9 +154,8 @@ + (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", (self.speed != nil ? self.speed : [NSNull null]), - @"speed", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.speed ? self.speed : [NSNull null]), @"speed", nil]; } @end @@ -176,10 +174,9 @@ + (FLTPositionMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.textureId != nil ? self.textureId : [NSNull null]), - @"textureId", - (self.position != nil ? self.position : [NSNull null]), - @"position", nil]; + dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", + (self.position ? self.position : [NSNull null]), @"position", + nil]; } @end @@ -194,7 +191,7 @@ + (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict { } - (NSDictionary *)toMap { return [NSDictionary - dictionaryWithObjectsAndKeys:(self.mixWithOthers != nil ? self.mixWithOthers : [NSNull null]), + dictionaryWithObjectsAndKeys:(self.mixWithOthers ? self.mixWithOthers : [NSNull null]), @"mixWithOthers", nil]; } @end @@ -365,3 +362,4 @@ void FLTVideoPlayerApiSetup(id binaryMessenger, id _parseCaptionsFromSubRipString(String file) { end: startAndEnd.end, text: text, ); - - if (newCaption.start != null && newCaption.end != null) { + if (newCaption.start != newCaption.end) { captions.add(newCaption); } } @@ -64,7 +63,7 @@ class _StartAndEnd { RegExp(_subRipTimeStamp + _subRipArrow + _subRipTimeStamp); if (!format.hasMatch(line)) { - return _StartAndEnd(null, null); + return _StartAndEnd(Duration.zero, Duration.zero); } final List times = line.split(_subRipArrow); @@ -84,7 +83,7 @@ class _StartAndEnd { // Duration(hours: 0, minutes: 1, seconds: 59, milliseconds: 084) Duration _parseSubRipTimestamp(String timestampString) { if (!RegExp(_subRipTimeStamp).hasMatch(timestampString)) { - return null; + return Duration.zero; } final List commaSections = timestampString.split(','); diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index ac1645085e36..6a2af76fa547 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -28,11 +28,12 @@ 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, - this.position = const Duration(), - this.caption = const Caption(), + required this.duration, + this.size = Size.zero, + this.position = Duration.zero, + this.caption = Caption.none, this.buffered = const [], + this.isInitialized = false, this.isPlaying = false, this.isLooping = false, this.isBuffering = false, @@ -41,17 +42,20 @@ class VideoPlayerValue { this.errorDescription, }); - /// Returns an instance with a `null` [Duration]. - VideoPlayerValue.uninitialized() : this(duration: null); + /// Returns an instance for a video that hasn't been loaded. + VideoPlayerValue.uninitialized() + : this(duration: Duration.zero, isInitialized: false); - /// Returns an instance with a `null` [Duration] and the given - /// [errorDescription]. + /// Returns an instance with the given [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) - : this(duration: null, errorDescription: errorDescription); + : this( + duration: Duration.zero, + isInitialized: false, + errorDescription: errorDescription); /// The total duration of the video. /// - /// Is null when [initialized] is false. + /// The duration is [Duration.zero] if the video hasn't been initialized. final Duration duration; /// The current playback position. @@ -60,7 +64,7 @@ class VideoPlayerValue { /// 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 an empty [Caption] object. + /// [position], this will be a [Caption.none] object. final Caption caption; /// The currently buffered ranges. @@ -84,7 +88,7 @@ class VideoPlayerValue { /// A description of the error if present. /// /// If [hasError] is false this is [null]. - final String errorDescription; + final String? errorDescription; /// The [size] of the currently loaded video. /// @@ -92,7 +96,7 @@ class VideoPlayerValue { final Size size; /// Indicates whether or not the video has been loaded and is ready to play. - bool get initialized => duration != null; + 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. @@ -101,7 +105,7 @@ class VideoPlayerValue { /// Returns [size.width] / [size.height] when size is non-null, or `1.0.` when /// size is null or the aspect ratio would be less than or equal to 0.0. double get aspectRatio { - if (size == null || size.width == 0 || size.height == 0) { + if (!isInitialized || size.width == 0 || size.height == 0) { return 1.0; } final double aspectRatio = size.width / size.height; @@ -114,17 +118,18 @@ class VideoPlayerValue { /// 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, - Caption caption, - List buffered, - bool isPlaying, - bool isLooping, - bool isBuffering, - double volume, - double playbackSpeed, - String errorDescription, + Duration? duration, + Size? size, + Duration? position, + Caption? caption, + List? buffered, + bool? isInitialized, + bool? isPlaying, + bool? isLooping, + bool? isBuffering, + double? volume, + double? playbackSpeed, + String? errorDescription, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -132,6 +137,7 @@ class VideoPlayerValue { position: position ?? this.position, caption: caption ?? this.caption, buffered: buffered ?? this.buffered, + isInitialized: isInitialized ?? this.isInitialized, isPlaying: isPlaying ?? this.isPlaying, isLooping: isLooping ?? this.isLooping, isBuffering: isBuffering ?? this.isBuffering, @@ -149,6 +155,7 @@ class VideoPlayerValue { 'position: $position, ' 'caption: $caption, ' 'buffered: [${buffered.join(', ')}], ' + 'isInitialized: $isInitialized, ' 'isPlaying: $isPlaying, ' 'isLooping: $isLooping, ' 'isBuffering: $isBuffering, ' @@ -178,7 +185,7 @@ class VideoPlayerController extends ValueNotifier { {this.package, this.closedCaptionFile, this.videoPlayerOptions}) : dataSourceType = DataSourceType.asset, formatHint = null, - super(VideoPlayerValue(duration: null)); + super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [VideoPlayerController] playing a video from obtained from /// the network. @@ -191,7 +198,7 @@ class VideoPlayerController extends ValueNotifier { {this.formatHint, this.closedCaptionFile, this.videoPlayerOptions}) : dataSourceType = DataSourceType.network, package = null, - super(VideoPlayerValue(duration: null)); + super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [VideoPlayerController] playing a video from a file. /// @@ -203,9 +210,7 @@ class VideoPlayerController extends ValueNotifier { dataSourceType = DataSourceType.file, package = null, formatHint = null, - super(VideoPlayerValue(duration: null)); - - int _textureId; + 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. @@ -213,31 +218,36 @@ class VideoPlayerController extends ValueNotifier { /// **Android only**. Will override the platform's generic file format /// detection with whatever is set here. - final VideoFormat formatHint; + 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; + final VideoPlayerOptions? videoPlayerOptions; /// Only set for [asset] videos. The package that the asset was loaded from. - final String package; + final String? package; /// Optional field to specify a file containing the closed /// captioning. /// /// This future will be awaited and the file will be loaded when /// [initialize()] is called. - final Future closedCaptionFile; + final Future? closedCaptionFile; - ClosedCaptionFile _closedCaptionFile; - Timer _timer; + ClosedCaptionFile? _closedCaptionFile; + Timer? _timer; bool _isDisposed = false; - Completer _creatingCompleter; - StreamSubscription _eventSubscription; - _VideoAppLifeCycleObserver _lifeCycleObserver; + Completer? _creatingCompleter; + StreamSubscription? _eventSubscription; + late _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. @@ -250,7 +260,7 @@ class VideoPlayerController extends ValueNotifier { _lifeCycleObserver.initialize(); _creatingCompleter = Completer(); - DataSource dataSourceDescription; + late DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: dataSourceDescription = DataSource( @@ -276,11 +286,12 @@ class VideoPlayerController extends ValueNotifier { if (videoPlayerOptions?.mixWithOthers != null) { await _videoPlayerPlatform - .setMixWithOthers(videoPlayerOptions.mixWithOthers); + .setMixWithOthers(videoPlayerOptions!.mixWithOthers); } - _textureId = await _videoPlayerPlatform.create(dataSourceDescription); - _creatingCompleter.complete(null); + _textureId = (await _videoPlayerPlatform.create(dataSourceDescription)) ?? + kUninitializedTextureId; + _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); void eventListener(VideoEvent event) { @@ -293,6 +304,7 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith( duration: event.duration, size: event.size, + isInitialized: event.duration != null, ); initializingCompleter.complete(null); _applyLooping(); @@ -325,8 +337,8 @@ class VideoPlayerController extends ValueNotifier { } void errorListener(Object obj) { - final PlatformException e = obj; - value = VideoPlayerValue.erroneous(e.message); + final PlatformException e = obj as PlatformException; + value = VideoPlayerValue.erroneous(e.message!); _timer?.cancel(); if (!initializingCompleter.isCompleted) { initializingCompleter.completeError(obj); @@ -342,7 +354,7 @@ class VideoPlayerController extends ValueNotifier { @override Future dispose() async { if (_creatingCompleter != null) { - await _creatingCompleter.future; + await _creatingCompleter!.future; if (!_isDisposed) { _isDisposed = true; _timer?.cancel(); @@ -379,14 +391,14 @@ class VideoPlayerController extends ValueNotifier { } Future _applyLooping() async { - if (!value.initialized || _isDisposed) { + if (!value.isInitialized || _isDisposed) { return; } await _videoPlayerPlatform.setLooping(_textureId, value.isLooping); } Future _applyPlayPause() async { - if (!value.initialized || _isDisposed) { + if (!value.isInitialized || _isDisposed) { return; } if (value.isPlaying) { @@ -400,8 +412,8 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposed) { return; } - final Duration newPosition = await position; - if (_isDisposed) { + final Duration? newPosition = await position; + if (newPosition == null) { return; } _updatePosition(newPosition); @@ -419,14 +431,14 @@ class VideoPlayerController extends ValueNotifier { } Future _applyVolume() async { - if (!value.initialized || _isDisposed) { + if (!value.isInitialized || _isDisposed) { return; } await _videoPlayerPlatform.setVolume(_textureId, value.volume); } Future _applyPlaybackSpeed() async { - if (!value.initialized || _isDisposed) { + if (!value.isInitialized || _isDisposed) { return; } @@ -442,7 +454,7 @@ class VideoPlayerController extends ValueNotifier { } /// The position in the current video. - Future get position async { + Future get position async { if (_isDisposed) { return null; } @@ -519,17 +531,17 @@ class VideoPlayerController extends ValueNotifier { /// [Caption]. Caption _getCaptionAt(Duration position) { if (_closedCaptionFile == null) { - return Caption(); + return Caption.none; } // TODO: This would be more efficient as a binary search. - for (final caption in _closedCaptionFile.captions) { + for (final caption in _closedCaptionFile!.captions) { if (caption.start <= position && caption.end >= position) { return caption; } } - return Caption(); + return Caption.none; } void _updatePosition(Duration position) { @@ -545,7 +557,7 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { final VideoPlayerController _controller; void initialize() { - WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance!.addObserver(this); } @override @@ -565,7 +577,7 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { } void dispose() { - WidgetsBinding.instance.removeObserver(this); + WidgetsBinding.instance!.removeObserver(this); } } @@ -594,8 +606,9 @@ class _VideoPlayerState extends State { }; } - VoidCallback _listener; - int _textureId; + late VoidCallback _listener; + + late int _textureId; @override void initState() { @@ -622,7 +635,7 @@ class _VideoPlayerState extends State { @override Widget build(BuildContext context) { - return _textureId == null + return _textureId == VideoPlayerController.kUninitializedTextureId ? Container() : _videoPlayerPlatform.buildView(_textureId); } @@ -646,7 +659,7 @@ class VideoProgressColors { /// [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. - VideoProgressColors({ + 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), @@ -670,8 +683,8 @@ class VideoProgressColors { class _VideoScrubber extends StatefulWidget { _VideoScrubber({ - @required this.child, - @required this.controller, + required this.child, + required this.controller, }); final Widget child; @@ -689,7 +702,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { - final RenderBox box = context.findRenderObject(); + 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; @@ -700,7 +713,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { behavior: HitTestBehavior.opaque, child: widget.child, onHorizontalDragStart: (DragStartDetails details) { - if (!controller.value.initialized) { + if (!controller.value.isInitialized) { return; } _controllerWasPlaying = controller.value.isPlaying; @@ -709,7 +722,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { } }, onHorizontalDragUpdate: (DragUpdateDetails details) { - if (!controller.value.initialized) { + if (!controller.value.isInitialized) { return; } seekToRelativePosition(details.globalPosition); @@ -720,7 +733,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { } }, onTapDown: (TapDownDetails details) { - if (!controller.value.initialized) { + if (!controller.value.isInitialized) { return; } seekToRelativePosition(details.globalPosition); @@ -745,10 +758,10 @@ class VideoProgressIndicator extends StatefulWidget { /// to `top: 5.0`. VideoProgressIndicator( this.controller, { - VideoProgressColors colors, - this.allowScrubbing, + this.colors = const VideoProgressColors(), + required this.allowScrubbing, this.padding = const EdgeInsets.only(top: 5.0), - }) : colors = colors ?? VideoProgressColors(); + }); /// The [VideoPlayerController] that actually associates a video with this /// widget. @@ -785,7 +798,7 @@ class _VideoProgressIndicatorState extends State { }; } - VoidCallback listener; + late VoidCallback listener; VideoPlayerController get controller => widget.controller; @@ -806,7 +819,7 @@ class _VideoProgressIndicatorState extends State { @override Widget build(BuildContext context) { Widget progressIndicator; - if (controller.value.initialized) { + if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; @@ -878,17 +891,17 @@ class ClosedCaption extends StatelessWidget { /// [VideoPlayerValue.caption]. /// /// If [text] is null, nothing will be displayed. - const ClosedCaption({Key key, this.text, this.textStyle}) : super(key: key); + 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. - final String text; + 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; + final TextStyle? textStyle; @override Widget build(BuildContext context) { @@ -913,7 +926,7 @@ class ClosedCaption extends StatelessWidget { ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 2.0), - child: Text(text, style: effectiveTextStyle), + child: Text(text!, style: effectiveTextStyle), ), ), ), diff --git a/packages/video_player/video_player/pigeons/messages.dart b/packages/video_player/video_player/pigeons/messages.dart index 427aea279071..f1771afecb45 100644 --- a/packages/video_player/video_player/pigeons/messages.dart +++ b/packages/video_player/video_player/pigeons/messages.dart @@ -1,3 +1,5 @@ +// @dart = 2.9 + import 'package:pigeon/pigeon_lib.dart'; class TextureMessage { diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 69be8b24100b..603f2358cd55 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -1,7 +1,10 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. -version: 1.0.1 +# 0.10.y+z is compatible with 1.0.0, if you land a breaking change bump +# the version to 2.0.0. +# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 +version: 2.0.0-nullsafety.5 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: @@ -16,15 +19,15 @@ flutter: default_package: video_player_web dependencies: - meta: ^1.0.5 - video_player_platform_interface: ^2.2.0 + meta: ^1.3.0-nullsafety.3 + video_player_platform_interface: ^3.0.0-nullsafety.3 # The design on https://flutter.dev/go/federated-plugins was to leave # this constraint as "any". We cannot do it right now as it fails pub publish # validation, so we set a ^ constraint. # TODO(amirh): Revisit this (either update this part in the design or the pub tool). # https://github.com/flutter/flutter/issues/46264 - video_player_web: '>=0.1.4 <2.0.0' + video_player_web: ^2.0.0-nullsafety.1 flutter: sdk: flutter @@ -32,9 +35,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 pigeon: 0.1.7 environment: - sdk: ">=2.8.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.13+hotfix.5" diff --git a/packages/video_player/video_player/test/sub_rip_file_test.dart b/packages/video_player/video_player/test/sub_rip_file_test.dart index cf25ff73e438..2b9803d8275e 100644 --- a/packages/video_player/video_player/test/sub_rip_file_test.dart +++ b/packages/video_player/video_player/test/sub_rip_file_test.dart @@ -108,6 +108,6 @@ This one is valid 3 00:01:54,724 --> 00:01:6,760 -This one should be ignored because the +This one should be ignored because the ned time is missing a digit. '''; diff --git a/packages/video_player/video_player/test/video_player_initialization_test.dart b/packages/video_player/video_player/test/video_player_initialization_test.dart index 231c399bb8fe..1a09ed9f718c 100644 --- a/packages/video_player/video_player/test/video_player_initialization_test.dart +++ b/packages/video_player/video_player/test/video_player_initialization_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:video_player/video_player.dart'; diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index a7b23c3b1f27..3e9800f2b68e 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; import 'dart:io'; @@ -18,7 +16,7 @@ import 'package:video_player_platform_interface/video_player_platform_interface. class FakeController extends ValueNotifier implements VideoPlayerController { - FakeController() : super(VideoPlayerValue(duration: null)); + FakeController() : super(VideoPlayerValue(duration: Duration.zero)); @override Future dispose() async { @@ -26,7 +24,7 @@ class FakeController extends ValueNotifier } @override - int textureId; + int textureId = VideoPlayerController.kUninitializedTextureId; @override String get dataSource => ''; @@ -35,7 +33,7 @@ class FakeController extends ValueNotifier DataSourceType get dataSourceType => DataSourceType.file; @override - String get package => null; + String get package => ''; @override Future get position async => value.position; @@ -62,13 +60,13 @@ class FakeController extends ValueNotifier Future setLooping(bool looping) async {} @override - VideoFormat get formatHint => null; + VideoFormat? get formatHint => null; @override Future get closedCaptionFile => _loadClosedCaption(); @override - VideoPlayerOptions get videoPlayerOptions => null; + VideoPlayerOptions? get videoPlayerOptions => null; } Future _loadClosedCaption() async => @@ -80,11 +78,13 @@ class _FakeClosedCaptionFile extends ClosedCaptionFile { return [ Caption( text: 'one', + number: 0, start: Duration(milliseconds: 100), end: Duration(milliseconds: 200), ), Caption( text: 'two', + number: 1, start: Duration(milliseconds: 300), end: Duration(milliseconds: 400), ), @@ -101,6 +101,7 @@ void main() { controller.textureId = 123; controller.value = controller.value.copyWith( duration: const Duration(milliseconds: 100), + isInitialized: true, ); await tester.pump(); @@ -133,8 +134,8 @@ void main() { await tester.pumpWidget(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); + expect(textWidget.style!.fontSize, 36.0); + expect(textWidget.style!.color, Colors.white); }); testWidgets('uses given text and style', (WidgetTester tester) async { @@ -149,7 +150,7 @@ void main() { expect(find.text(text), findsOneWidget); final Text textWidget = tester.widget(find.text(text)); - expect(textWidget.style.fontSize, textStyle.fontSize); + expect(textWidget.style!.fontSize, textStyle.fontSize); }); testWidgets('handles null text', (WidgetTester tester) async { @@ -173,7 +174,7 @@ void main() { }); group('VideoPlayerController', () { - FakeVideoPlayerPlatform fakeVideoPlayerPlatform; + late FakeVideoPlayerPlatform fakeVideoPlayerPlatform; setUp(() { fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); @@ -221,7 +222,7 @@ void main() { 'http://testing.com/invalid_url', ); try { - dynamic error; + late dynamic error; fakeVideoPlayerPlatform.forceInitError = true; await controller.initialize().catchError((dynamic e) => error = e); final PlatformException platformEx = error; @@ -245,13 +246,14 @@ void main() { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); - expect(controller.textureId, isNull); + expect( + controller.textureId, VideoPlayerController.kUninitializedTextureId); expect(await controller.position, const Duration(seconds: 0)); await controller.initialize(); await controller.dispose(); - expect(controller.textureId, isNotNull); + expect(controller.textureId, 0); expect(await controller.position, isNull); }); @@ -390,19 +392,19 @@ void main() { await controller.initialize(); expect(controller.value.position, const Duration()); - expect(controller.value.caption.text, isNull); + 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, isNull); + expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 500)); - expect(controller.value.caption.text, isNull); + expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); @@ -419,8 +421,7 @@ void main() { await controller.play(); expect(controller.value.isPlaying, isTrue); final FakeVideoEventStream fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]; - assert(fakeVideoEventStream != null); + fakeVideoPlayerPlatform.streams[controller.textureId]!; fakeVideoEventStream.eventsChannel .sendEvent({'event': 'completed'}); @@ -438,8 +439,7 @@ void main() { expect(controller.value.isBuffering, false); expect(controller.value.buffered, isEmpty); final FakeVideoEventStream fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]; - assert(fakeVideoEventStream != null); + fakeVideoPlayerPlatform.streams[controller.textureId]!; fakeVideoEventStream.eventsChannel .sendEvent({'event': 'bufferingStart'}); @@ -496,9 +496,9 @@ void main() { test('uninitialized()', () { final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized(); - expect(uninitialized.duration, isNull); - expect(uninitialized.position, equals(const Duration(seconds: 0))); - expect(uninitialized.caption, equals(const Caption())); + expect(uninitialized.duration, equals(Duration.zero)); + expect(uninitialized.position, equals(Duration.zero)); + expect(uninitialized.caption, equals(Caption.none)); expect(uninitialized.buffered, isEmpty); expect(uninitialized.isPlaying, isFalse); expect(uninitialized.isLooping, isFalse); @@ -506,9 +506,8 @@ void main() { expect(uninitialized.volume, 1.0); expect(uninitialized.playbackSpeed, 1.0); expect(uninitialized.errorDescription, isNull); - expect(uninitialized.size, isNull); - expect(uninitialized.size, isNull); - expect(uninitialized.initialized, isFalse); + expect(uninitialized.size, equals(Size.zero)); + expect(uninitialized.isInitialized, isFalse); expect(uninitialized.hasError, isFalse); expect(uninitialized.aspectRatio, 1.0); }); @@ -517,9 +516,9 @@ void main() { const String errorMessage = 'foo'; final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage); - expect(error.duration, isNull); - expect(error.position, equals(const Duration(seconds: 0))); - expect(error.caption, equals(const Caption())); + expect(error.duration, equals(Duration.zero)); + expect(error.position, equals(Duration.zero)); + expect(error.caption, equals(Caption.none)); expect(error.buffered, isEmpty); expect(error.isPlaying, isFalse); expect(error.isLooping, isFalse); @@ -527,9 +526,8 @@ void main() { expect(error.volume, 1.0); expect(error.playbackSpeed, 1.0); expect(error.errorDescription, errorMessage); - expect(error.size, isNull); - expect(error.size, isNull); - expect(error.initialized, isFalse); + expect(error.size, equals(Size.zero)); + expect(error.isInitialized, isFalse); expect(error.hasError, isTrue); expect(error.aspectRatio, 1.0); }); @@ -538,10 +536,12 @@ void main() { const Duration duration = Duration(seconds: 5); const Size size = Size(400, 300); const Duration position = Duration(seconds: 1); - const Caption caption = Caption(text: 'foo'); + const Caption caption = Caption( + text: 'foo', number: 0, start: Duration.zero, end: Duration.zero); final List buffered = [ DurationRange(const Duration(seconds: 0), const Duration(seconds: 4)) ]; + const bool isInitialized = true; const bool isPlaying = true; const bool isLooping = true; const bool isBuffering = true; @@ -554,6 +554,7 @@ void main() { position: position, caption: caption, buffered: buffered, + isInitialized: isInitialized, isPlaying: isPlaying, isLooping: isLooping, isBuffering: isBuffering, @@ -568,6 +569,7 @@ void main() { 'position: 0:00:01.000000, ' 'caption: Caption(number: null, start: null, end: null, text: foo), ' 'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], ' + 'isInitialized: true, ' 'isPlaying: true, ' 'isLooping: true, ' 'isBuffering: true, ' @@ -586,15 +588,16 @@ void main() { group('aspectRatio', () { test('640x480 -> 4:3', () { final value = VideoPlayerValue( + isInitialized: true, size: Size(640, 480), duration: Duration(seconds: 1), ); expect(value.aspectRatio, 4 / 3); }); - test('null size -> 1.0', () { + test('no size -> 1.0', () { final value = VideoPlayerValue( - size: null, + isInitialized: true, duration: Duration(seconds: 1), ); expect(value.aspectRatio, 1.0); @@ -602,6 +605,7 @@ void main() { test('height = 0 -> 1.0', () { final value = VideoPlayerValue( + isInitialized: true, size: Size(640, 0), duration: Duration(seconds: 1), ); @@ -610,6 +614,7 @@ void main() { test('width = 0 -> 1.0', () { final value = VideoPlayerValue( + isInitialized: true, size: Size(0, 480), duration: Duration(seconds: 1), ); @@ -618,6 +623,7 @@ void main() { test('negative aspect ratio -> 1.0', () { final value = VideoPlayerValue( + isInitialized: true, size: Size(640, -480), duration: Duration(seconds: 1), ); @@ -646,7 +652,7 @@ void main() { File(''), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); await controller.initialize(); - expect(controller.videoPlayerOptions.mixWithOthers, true); + expect(controller.videoPlayerOptions!.mixWithOthers, true); }); } @@ -706,7 +712,7 @@ class FakeVideoPlayerPlatform extends TestHostVideoPlayerApi { @override void seekTo(PositionMessage arg) { calls.add('seekTo'); - _positions[arg.textureId] = Duration(milliseconds: arg.position); + _positions[arg.textureId!] = Duration(milliseconds: arg.position!); } @override @@ -742,7 +748,7 @@ class FakeVideoEventStream { int height; Duration duration; bool initWithError; - FakeEventsChannel eventsChannel; + late FakeEventsChannel eventsChannel; void onListen() { if (!initWithError) { @@ -764,7 +770,7 @@ class FakeEventsChannel { eventsMethodChannel.setMockMethodCallHandler(onMethodCall); } - MethodChannel eventsMethodChannel; + late MethodChannel eventsMethodChannel; VoidCallback onListen; Future onMethodCall(MethodCall call) { @@ -780,7 +786,7 @@ class FakeEventsChannel { _sendMessage(const StandardMethodCodec().encodeSuccessEnvelope(event)); } - void sendError(String code, [String message, dynamic details]) { + void sendError(String code, [String? message, dynamic details]) { _sendMessage(const StandardMethodCodec().encodeErrorEnvelope( code: code, message: message, @@ -794,6 +800,6 @@ class FakeEventsChannel { // available on all the versions of Flutter that we test. // ignore: deprecated_member_use defaultBinaryMessenger.handlePlatformMessage( - eventsMethodChannel.name, data, (ByteData data) {}); + eventsMethodChannel.name, data, (ByteData? data) {}); } } diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 8af22f783675..446fffd9a60e 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,3 +1,23 @@ +## 3.0.0-nullsafety.3 + +* `messages.dart` sets Dart `2.12`. + +## 3.0.0-nullsafety.2 + +* Bump Dart SDK to support null safety. + +## 3.0.0-nullsafety.1 + +* Make DataSource's `uri` parameter nullable. + +## 3.0.0-nullsafety + +* Migrate to null safety. + +## 2.2.1 + +* Update Flutter SDK constraint. + ## 2.2.0 * Added option to set the video playback speed on the video controller. diff --git a/packages/video_player/video_player_platform_interface/lib/messages.dart b/packages/video_player/video_player_platform_interface/lib/messages.dart index bfe65f1fd2ea..252cad6993ca 100644 --- a/packages/video_player/video_player_platform_interface/lib/messages.dart +++ b/packages/video_player/video_player_platform_interface/lib/messages.dart @@ -1,13 +1,13 @@ -// Autogenerated from Pigeon (v0.1.7), do not edit directly. +// Autogenerated from Pigeon (v0.1.12), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import -// @dart = 2.8 +// @dart = 2.12 import 'dart:async'; import 'package:flutter/services.dart'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; class TextureMessage { - int textureId; + int? textureId; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -17,9 +17,6 @@ class TextureMessage { // ignore: unused_element static TextureMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final TextureMessage result = TextureMessage(); result.textureId = pigeonMap['textureId']; return result; @@ -27,10 +24,10 @@ class TextureMessage { } class CreateMessage { - String asset; - String uri; - String packageName; - String formatHint; + String? asset; + String? uri; + String? packageName; + String? formatHint; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -43,9 +40,6 @@ class CreateMessage { // ignore: unused_element static CreateMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final CreateMessage result = CreateMessage(); result.asset = pigeonMap['asset']; result.uri = pigeonMap['uri']; @@ -56,8 +50,8 @@ class CreateMessage { } class LoopingMessage { - int textureId; - bool isLooping; + int? textureId; + bool? isLooping; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -68,9 +62,6 @@ class LoopingMessage { // ignore: unused_element static LoopingMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final LoopingMessage result = LoopingMessage(); result.textureId = pigeonMap['textureId']; result.isLooping = pigeonMap['isLooping']; @@ -79,8 +70,8 @@ class LoopingMessage { } class VolumeMessage { - int textureId; - double volume; + int? textureId; + double? volume; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -91,9 +82,6 @@ class VolumeMessage { // ignore: unused_element static VolumeMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final VolumeMessage result = VolumeMessage(); result.textureId = pigeonMap['textureId']; result.volume = pigeonMap['volume']; @@ -102,8 +90,8 @@ class VolumeMessage { } class PlaybackSpeedMessage { - int textureId; - double speed; + int? textureId; + double? speed; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -114,9 +102,6 @@ class PlaybackSpeedMessage { // ignore: unused_element static PlaybackSpeedMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final PlaybackSpeedMessage result = PlaybackSpeedMessage(); result.textureId = pigeonMap['textureId']; result.speed = pigeonMap['speed']; @@ -125,8 +110,8 @@ class PlaybackSpeedMessage { } class PositionMessage { - int textureId; - int position; + int? textureId; + int? position; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -137,9 +122,6 @@ class PositionMessage { // ignore: unused_element static PositionMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final PositionMessage result = PositionMessage(); result.textureId = pigeonMap['textureId']; result.position = pigeonMap['position']; @@ -148,7 +130,7 @@ class PositionMessage { } class MixWithOthersMessage { - bool mixWithOthers; + bool? mixWithOthers; // ignore: unused_element Map _toMap() { final Map pigeonMap = {}; @@ -158,9 +140,6 @@ class MixWithOthersMessage { // ignore: unused_element static MixWithOthersMessage _fromMap(Map pigeonMap) { - if (pigeonMap == null) { - return null; - } final MixWithOthersMessage result = MixWithOthersMessage(); result.mixWithOthers = pigeonMap['mixWithOthers']; return result; @@ -172,7 +151,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec()); - final Map replyMap = await channel.send(null); + final Map? replyMap = await channel.send(null); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -194,7 +173,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.create', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -216,7 +195,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.dispose', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -238,7 +217,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setLooping', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -260,7 +239,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setVolume', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -283,7 +262,7 @@ class VideoPlayerApi { 'dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -305,7 +284,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.play', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -327,7 +306,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -349,7 +328,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.seekTo', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -371,7 +350,7 @@ class VideoPlayerApi { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.pause', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -394,7 +373,7 @@ class VideoPlayerApi { 'dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers', StandardMessageCodec()); - final Map replyMap = await channel.send(requestMap); + final Map? replyMap = await channel.send(requestMap); if (replyMap == null) { throw PlatformException( code: 'channel-error', @@ -424,131 +403,175 @@ abstract class TestHostVideoPlayerApi { void seekTo(PositionMessage arg); void pause(TextureMessage arg); void setMixWithOthers(MixWithOthersMessage arg); - static void setup(TestHostVideoPlayerApi api) { + static void setup(TestHostVideoPlayerApi? api) { { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - api.initialize(); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + api.initialize(); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.create', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final CreateMessage input = CreateMessage._fromMap(mapMessage); - final TextureMessage output = api.create(input); - return {'result': output._toMap()}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final CreateMessage input = CreateMessage._fromMap(mapMessage); + final TextureMessage output = api.create(input); + return {'result': output._toMap()}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.dispose', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final TextureMessage input = TextureMessage._fromMap(mapMessage); - api.dispose(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final TextureMessage input = TextureMessage._fromMap(mapMessage); + api.dispose(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setLooping', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final LoopingMessage input = LoopingMessage._fromMap(mapMessage); - api.setLooping(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final LoopingMessage input = LoopingMessage._fromMap(mapMessage); + api.setLooping(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setVolume', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final VolumeMessage input = VolumeMessage._fromMap(mapMessage); - api.setVolume(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final VolumeMessage input = VolumeMessage._fromMap(mapMessage); + api.setVolume(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final PlaybackSpeedMessage input = - PlaybackSpeedMessage._fromMap(mapMessage); - api.setPlaybackSpeed(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final PlaybackSpeedMessage input = + PlaybackSpeedMessage._fromMap(mapMessage); + api.setPlaybackSpeed(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.play', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final TextureMessage input = TextureMessage._fromMap(mapMessage); - api.play(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final TextureMessage input = TextureMessage._fromMap(mapMessage); + api.play(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final TextureMessage input = TextureMessage._fromMap(mapMessage); - final PositionMessage output = api.position(input); - return {'result': output._toMap()}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final TextureMessage input = TextureMessage._fromMap(mapMessage); + final PositionMessage output = api.position(input); + return {'result': output._toMap()}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.seekTo', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final PositionMessage input = PositionMessage._fromMap(mapMessage); - api.seekTo(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final PositionMessage input = PositionMessage._fromMap(mapMessage); + api.seekTo(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.pause', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final TextureMessage input = TextureMessage._fromMap(mapMessage); - api.pause(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final TextureMessage input = TextureMessage._fromMap(mapMessage); + api.pause(input); + return {}; + }); + } } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers', StandardMessageCodec()); - channel.setMockMessageHandler((dynamic message) async { - final Map mapMessage = - message as Map; - final MixWithOthersMessage input = - MixWithOthersMessage._fromMap(mapMessage); - api.setMixWithOthers(input); - return {}; - }); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = + message as Map; + final MixWithOthersMessage input = + MixWithOthersMessage._fromMap(mapMessage); + api.setMixWithOthers(input); + return {}; + }); + } } } } diff --git a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart index 0ea443fb6e12..9b007d00d6a9 100644 --- a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart +++ b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart @@ -26,7 +26,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { } @override - Future create(DataSource dataSource) async { + Future create(DataSource dataSource) async { CreateMessage message = CreateMessage(); switch (dataSource.sourceType) { @@ -91,7 +91,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { Future getPosition(int textureId) async { PositionMessage response = await _api.position(TextureMessage()..textureId = textureId); - return Duration(milliseconds: response.position); + return Duration(milliseconds: response.position!); } @override diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 2757fb135af6..f2bc00205acc 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -7,7 +7,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart' show required, visibleForTesting; +import 'package:meta/meta.dart' show visibleForTesting; import 'method_channel_video_player.dart'; @@ -66,7 +66,7 @@ abstract class VideoPlayerPlatform { } /// Creates an instance of a video player and returns its textureId. - Future create(DataSource dataSource) { + Future create(DataSource dataSource) { throw UnimplementedError('create() has not been implemented.'); } @@ -146,7 +146,7 @@ class DataSource { /// The [package] argument must be non-null when the asset comes from a /// package and null otherwise. DataSource({ - @required this.sourceType, + required this.sourceType, this.uri, this.formatHint, this.asset, @@ -163,18 +163,18 @@ class DataSource { /// /// This will be in different formats depending on the [DataSourceType] of /// the original video. - final String uri; + final String? uri; /// **Android only**. Will override the platform's generic file format /// detection with whatever is set here. - final VideoFormat formatHint; + final VideoFormat? formatHint; /// The name of the asset. Only set for [DataSourceType.asset] videos. - final String asset; + final String? asset; /// The package that the asset was loaded from. Only set for /// [DataSourceType.asset] videos. - final String package; + final String? package; } /// The way in which the video was originally loaded. @@ -216,7 +216,7 @@ class VideoEvent { /// Depending on the [eventType], the [duration], [size] and [buffered] /// arguments can be null. VideoEvent({ - @required this.eventType, + required this.eventType, this.duration, this.size, this.buffered, @@ -228,17 +228,17 @@ class VideoEvent { /// Duration of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. - final Duration duration; + final Duration? duration; /// Size of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. - final Size size; + final Size? size; /// Buffered parts of the video. /// /// Only used if [eventType] is [VideoEventType.bufferingUpdate]. - final List buffered; + final List? buffered; @override bool operator ==(Object other) { diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index cc3cd79f1f33..ea8d3179cf1d 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -3,19 +3,19 @@ description: A common platform interface for the video_player plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface # 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.0 +version: 3.0.0-nullsafety.3 dependencies: flutter: sdk: flutter - meta: ^1.0.5 + meta: ^1.3.0-nullsafety.3 dev_dependencies: flutter_test: sdk: flutter mockito: ^4.1.1 - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.8.0 <3.0.0" - flutter: ">=1.10.0 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.10.0" diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart index c4791001ad92..5c19ebca0d12 100644 --- a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart +++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(egarciad): Remove once Mockito is migrated to null safety. +// @dart = 2.9 + import 'dart:ui'; import 'package:flutter/services.dart'; diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index d8bebd3ae4b6..2becf5b85e0a 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.0.0-nullsafety.2 + +* Fixed an issue where `isBuffering` was not updating on Web. + +## 2.0.0-nullsafety.1 + +* Bump Dart SDK to support null safety. + +## 2.0.0-nullsafety + +* Migrate to null safety. + +## 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). diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 6715d5aca53b..9132a08437da 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -50,7 +50,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { @override Future dispose(int textureId) async { - _videoPlayers[textureId].dispose(); + _videoPlayers[textureId]!.dispose(); _videoPlayers.remove(textureId); return null; } @@ -66,16 +66,16 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { final int textureId = _textureCounter; _textureCounter++; - String uri; + 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; + uri = dataSource.uri ?? ''; break; case DataSourceType.asset: - String assetUrl = dataSource.asset; - if (dataSource.package != null && dataSource.package.isNotEmpty) { + String assetUrl = dataSource.asset!; + if (dataSource.package != null && dataSource.package!.isNotEmpty) { assetUrl = 'packages/${dataSource.package}/$assetUrl'; } assetUrl = ui.webOnlyAssetManager.getAssetUrl(assetUrl); @@ -99,45 +99,45 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { @override Future setLooping(int textureId, bool looping) async { - return _videoPlayers[textureId].setLooping(looping); + return _videoPlayers[textureId]!.setLooping(looping); } @override Future play(int textureId) async { - return _videoPlayers[textureId].play(); + return _videoPlayers[textureId]!.play(); } @override Future pause(int textureId) async { - return _videoPlayers[textureId].pause(); + return _videoPlayers[textureId]!.pause(); } @override Future setVolume(int textureId, double volume) async { - return _videoPlayers[textureId].setVolume(volume); + return _videoPlayers[textureId]!.setVolume(volume); } @override Future setPlaybackSpeed(int textureId, double speed) async { assert(speed > 0); - return _videoPlayers[textureId].setPlaybackSpeed(speed); + return _videoPlayers[textureId]!.setPlaybackSpeed(speed); } @override Future seekTo(int textureId, Duration position) async { - return _videoPlayers[textureId].seekTo(position); + return _videoPlayers[textureId]!.seekTo(position); } @override Future getPosition(int textureId) async { - _videoPlayers[textureId].sendBufferingUpdate(); - return _videoPlayers[textureId].getPosition(); + _videoPlayers[textureId]!.sendBufferingUpdate(); + return _videoPlayers[textureId]!.getPosition(); } @override Stream videoEventsFor(int textureId) { - return _videoPlayers[textureId].eventController.stream; + return _videoPlayers[textureId]!.eventController.stream; } @override @@ -147,15 +147,26 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { } class _VideoPlayer { - _VideoPlayer({this.uri, this.textureId}); + _VideoPlayer({required this.uri, required this.textureId}); final StreamController eventController = StreamController(); final String uri; final int textureId; - VideoElement videoElement; + late VideoElement videoElement; bool isInitialized = false; + bool isBuffering = false; + + void setBuffering(bool buffering) { + if (isBuffering != buffering) { + isBuffering = buffering; + eventController.add(VideoEvent( + eventType: isBuffering + ? VideoEventType.bufferingStart + : VideoEventType.bufferingEnd)); + } + } void initialize() { videoElement = VideoElement() @@ -176,22 +187,38 @@ class _VideoPlayer { isInitialized = true; sendInitialized(); } + setBuffering(false); + }); + + videoElement.onCanPlayThrough.listen((dynamic _) { + setBuffering(false); + }); + + videoElement.onPlaying.listen((dynamic _) { + setBuffering(false); + }); + + videoElement.onWaiting.listen((dynamic _) { + setBuffering(true); + sendBufferingUpdate(); }); // The error event fires when some form of error occurs while attempting to load or perform the media. videoElement.onError.listen((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 - MediaError error = videoElement.error; + MediaError error = videoElement.error!; eventController.addError(PlatformException( - code: _kErrorValueToErrorName[error.code], + 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)); }); } @@ -258,8 +285,8 @@ class _VideoPlayer { milliseconds: (videoElement.duration * 1000).round(), ), size: Size( - videoElement.videoWidth.toDouble() ?? 0.0, - videoElement.videoHeight.toDouble() ?? 0.0, + videoElement.videoWidth.toDouble(), + videoElement.videoHeight.toDouble(), ), ), ); diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index ae05bfbcf910..9333ac0ac6f0 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/v # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.4+1 +version: 2.0.0-nullsafety.2 flutter: plugin: @@ -18,16 +18,16 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - meta: ^1.1.7 - video_player_platform_interface: ^2.2.0 + meta: ^1.3.0-nullsafety.3 + video_player_platform_interface: ^3.0.0-nullsafety.3 dev_dependencies: flutter_test: sdk: flutter video_player: path: ../video_player - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 environment: - sdk: ">=2.8.0 <3.0.0" - flutter: ">=1.12.8 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.12.8" diff --git a/packages/video_player/video_player_web/test/video_player_web_test.dart b/packages/video_player/video_player_web/test/video_player_web_test.dart index 453079bfcd40..c433d82027f0 100644 --- a/packages/video_player/video_player_web/test/video_player_web_test.dart +++ b/packages/video_player/video_player_web/test/video_player_web_test.dart @@ -14,16 +14,16 @@ import 'package:video_player_web/video_player_web.dart'; void main() { group('VideoPlayer for Web', () { - int textureId; + late int textureId; setUp(() async { VideoPlayerPlatform.instance = VideoPlayerPlugin(); - textureId = await VideoPlayerPlatform.instance.create( + textureId = (await VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.network, uri: 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), - ); + ))!; }); test('$VideoPlayerPlugin is the live instance', () { @@ -84,12 +84,12 @@ void main() { }); test('throws PlatformException when playing bad media', () async { - int videoPlayerId = await VideoPlayerPlatform.instance.create( + int videoPlayerId = (await VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.network, uri: 'https://flutter.github.io/assets-for-api-docs/assets/videos/_non_existent_video.mp4'), - ); + ))!; Stream eventStream = VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 8f1e238f6919..19cacb6a6e90 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2.0.0-nullsafety.2 + +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) + +## 2.0.0-nullsafety.1 + +* Added `allowsInlineMediaPlayback` property. + +## 2.0.0-nullsafety + +* Migration to null-safety. + +## 1.0.8 + +* Update Flutter SDK constraint. + ## 1.0.7 * Minor documentation update to indicate known issue on iOS 13.4 and 13.5. diff --git a/packages/webview_flutter/README.md b/packages/webview_flutter/README.md index 6852ebb34522..df554eb5123c 100644 --- a/packages/webview_flutter/README.md +++ b/packages/webview_flutter/README.md @@ -1,6 +1,6 @@ # WebView for Flutter -[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dartlang.org/packages/webview_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. @@ -8,7 +8,7 @@ On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView). ## Usage -Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). You can now include a WebView widget in your widget tree. See the [WebView](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebView-class.html) diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index bfb79a39e8ba..ef9f006f6e5b 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -372,6 +372,9 @@ private void applySettings(Map settings) { case "userAgent": updateUserAgent((String) settings.get(key)); break; + case "allowsInlineMediaPlayback": + // no-op inline media playback is always allowed on Android. + break; default: throw new IllegalArgumentException("Unknown WebView setting: " + key); } diff --git a/packages/webview_flutter/example/assets/sample_video.mp4 b/packages/webview_flutter/example/assets/sample_video.mp4 new file mode 100644 index 000000000000..a203d0cdf13e Binary files /dev/null and b/packages/webview_flutter/example/assets/sample_video.mp4 differ diff --git a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart index 2a17c53a4c3f..86cf8ab73e05 100644 --- a/packages/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -120,16 +120,14 @@ void main() { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Echo', onMessageReceived: (JavascriptMessage message) { messagesReceived.add(message.message); }, ), - ].toSet(), + }, onPageStarted: (String url) { pageStarted.complete(null); }, @@ -180,16 +178,14 @@ void main() { onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Resize', onMessageReceived: (JavascriptMessage message) { resizeCompleter.complete(true); }, ), - ].toSet(), + }, onPageStarted: (String url) { pageStarted.complete(null); }, @@ -327,7 +323,218 @@ void main() { expect(customUserAgent2, defaultPlatformUserAgent); }); - group('Media playback policy', () { + group('Video playback policy', () { + 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 = ''' + + Codestin Search App + + + + + + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + test('Auto media playback', () async { + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + await 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.evaluateJavascript('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 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.require_user_action_for_all_media_types, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageLoaded.future; + + isPaused = await controller.evaluateJavascript('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + test('Changes to initialMediaPlaybackPolicy are ignored', () async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await 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.evaluateJavascript('isPaused();'); + expect(isPaused, _webviewBool(false)); + + pageLoaded = Completer(); + + await 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.require_user_action_for_all_media_types, + ), + ), + ); + + await controller.reload(); + + await pageLoaded.future; + + isPaused = await controller.evaluateJavascript('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + + test('Video plays inline when allowsInlineMediaPlayback is true', () async { + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + await 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, + allowsInlineMediaPlayback: true, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isFullScreen = + await controller.evaluateJavascript('isFullScreen();'); + expect(isFullScreen, _webviewBool(false)); + + controllerCompleter = Completer(); + pageLoaded = Completer(); + + await 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, + allowsInlineMediaPlayback: false, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageLoaded.future; + + isFullScreen = await controller.evaluateJavascript('isFullScreen();'); + expect(isFullScreen, _webviewBool(true)); + }); + }); + + group('Audio playback policy', () { String audioTestBase64; setUpAll(() async { final ByteData audioData = diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart index 7ec3008337d8..2a4b652d2658 100644 --- a/packages/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/example/lib/main.dart @@ -62,11 +62,9 @@ class _WebViewExampleState extends State { onWebViewCreated: (WebViewController webViewController) { _controller.complete(webViewController); }, - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { _toasterJavascriptChannel(context), - ].toSet(), + }, navigationDelegate: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { print('blocking navigation to $request}'); diff --git a/packages/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/example/pubspec.yaml index 44c740ae9739..543e7fd86971 100644 --- a/packages/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/example/pubspec.yaml @@ -23,3 +23,4 @@ flutter: uses-material-design: true assets: - assets/sample_audio.ogg + - assets/sample_video.mp4 diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m index 969e010913f3..ed3cf44424e8 100644 --- a/packages/webview_flutter/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m @@ -332,6 +332,9 @@ - (NSString*)applySettings:(NSDictionary*)settings { } else if ([key isEqualToString:@"userAgent"]) { NSString* userAgent = settings[key]; [self updateUserAgent:[userAgent isEqual:[NSNull null]] ? nil : userAgent]; + } else if ([key isEqualToString:@"allowsInlineMediaPlayback"]) { + NSNumber* allowsInlineMediaPlayback = settings[key]; + _webView.configuration.allowsInlineMediaPlayback = [allowsInlineMediaPlayback boolValue]; } else { [unknownKeys addObject:key]; } diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 6c991b14a76e..a840c0036fb3 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -21,7 +21,8 @@ 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({String url, bool isForMainFrame}); + FutureOr onNavigationRequest( + {required String url, required bool isForMainFrame}); /// Invoked by [WebViewPlatformController] when a page has started loading. void onPageStarted(String url); @@ -103,8 +104,8 @@ class WebResourceError { /// A user should not need to instantiate this class, but will receive one in /// [WebResourceErrorCallback]. WebResourceError({ - @required this.errorCode, - @required this.description, + required this.errorCode, + required this.description, this.domain, this.errorType, this.failingUrl, @@ -131,7 +132,7 @@ class WebResourceError { /// 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; + final String? domain; /// Description of the error that can be used to communicate the problem to the user. final String description; @@ -139,13 +140,13 @@ class WebResourceError { /// The type this error can be categorized as. /// /// This will never be `null` on Android, but can be `null` on iOS. - final WebResourceErrorType errorType; + 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; + final String? failingUrl; } /// Interface for talking to the webview's platform implementation. @@ -176,7 +177,7 @@ abstract class WebViewPlatformController { /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, - Map headers, + Map? headers, ) { throw UnimplementedError( "WebView loadUrl is not implemented on the current platform"); @@ -194,7 +195,7 @@ abstract class WebViewPlatformController { /// Accessor to the current URL that the WebView is displaying. /// /// If no URL was ever loaded, returns `null`. - Future currentUrl() { + Future currentUrl() { throw UnimplementedError( "WebView currentUrl is not implemented on the current platform"); } @@ -281,7 +282,7 @@ abstract class WebViewPlatformController { } /// Returns the title of the currently loaded page. - Future getTitle() { + Future getTitle() { throw UnimplementedError( "WebView getTitle is not implemented on the current platform"); } @@ -337,7 +338,7 @@ class WebSetting { : _value = value, isPresent = true; - final T _value; + final T? _value; /// The setting's value. /// @@ -347,7 +348,14 @@ class WebSetting { throw StateError('Cannot access a value of an absent WebSetting'); } assert(isPresent); - return _value; + // 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. @@ -358,7 +366,7 @@ class WebSetting { @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - final WebSetting typedOther = other; + final WebSetting typedOther = other as WebSetting; return typedOther.isPresent == isPresent && typedOther._value == _value; } @@ -382,19 +390,25 @@ class WebSettings { this.hasNavigationDelegate, this.debuggingEnabled, this.gestureNavigationEnabled, - @required this.userAgent, + this.allowsInlineMediaPlayback, + required this.userAgent, }) : assert(userAgent != null); /// The JavaScript execution mode to be used by the webview. - final JavascriptMode javascriptMode; + final JavascriptMode? javascriptMode; /// Whether the [WebView] has a [NavigationDelegate] set. - final bool hasNavigationDelegate; + final bool? hasNavigationDelegate; /// Whether to enable the platform's webview content debugging tools. /// /// See also: [WebView.debuggingEnabled]. - final bool 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. /// @@ -404,16 +418,16 @@ class WebSettings { /// last time it was set. /// /// See also [WebView.userAgent]. - final WebSetting userAgent; + final WebSetting userAgent; /// Whether to allow swipe based navigation in iOS. /// /// See also: [WebView.gestureNavigationEnabled] - final bool gestureNavigationEnabled; + final bool? gestureNavigationEnabled; @override String toString() { - return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent)'; + return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)'; } } @@ -428,7 +442,7 @@ class CreationParams { CreationParams({ this.initialUrl, this.webSettings, - this.javascriptChannelNames, + this.javascriptChannelNames = const {}, this.userAgent, this.autoMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, @@ -437,12 +451,12 @@ class CreationParams { /// The initialUrl to load in the webview. /// /// When null the webview will be created without loading any page. - final String initialUrl; + final String? initialUrl; /// The initial [WebSettings] for the new webview. /// /// This can later be updated with [WebViewPlatformController.updateSettings]. - final WebSettings webSettings; + final WebSettings? webSettings; /// The initial set of JavaScript channels that are configured for this webview. /// @@ -460,7 +474,7 @@ class CreationParams { /// 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; + final String? userAgent; /// Which restrictions apply on automatic media playback. final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; @@ -475,7 +489,7 @@ class CreationParams { /// /// See also the `onWebViewPlatformCreated` argument for [WebViewPlatform.build]. typedef WebViewPlatformCreatedCallback = void Function( - WebViewPlatformController webViewPlatformController); + WebViewPlatformController? webViewPlatformController); /// Interface for a platform implementation of a WebView. /// @@ -505,14 +519,14 @@ abstract class WebViewPlatform { /// /// `webViewPlatformHandler` must not be null. Widget build({ - BuildContext context, + 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. - CreationParams creationParams, - @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers, + required CreationParams creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, }); /// Clears all cookies for all [WebView] instances. diff --git a/packages/webview_flutter/lib/src/webview_android.dart b/packages/webview_flutter/lib/src/webview_android.dart index f7afcc0637a3..cba9e1b698b8 100644 --- a/packages/webview_flutter/lib/src/webview_android.dart +++ b/packages/webview_flutter/lib/src/webview_android.dart @@ -20,11 +20,11 @@ import 'webview_method_channel.dart'; class AndroidWebView implements WebViewPlatform { @override Widget build({ - BuildContext context, - CreationParams creationParams, - @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers, + required BuildContext context, + required CreationParams creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, }) { assert(webViewPlatformCallbacksHandler != null); return GestureDetector( diff --git a/packages/webview_flutter/lib/src/webview_cupertino.dart b/packages/webview_flutter/lib/src/webview_cupertino.dart index 0e84908261e4..e6816555f73b 100644 --- a/packages/webview_flutter/lib/src/webview_cupertino.dart +++ b/packages/webview_flutter/lib/src/webview_cupertino.dart @@ -20,11 +20,11 @@ import 'webview_method_channel.dart'; class CupertinoWebView implements WebViewPlatform { @override Widget build({ - BuildContext context, - CreationParams creationParams, - @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers, + required BuildContext context, + required CreationParams creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, }) { return UiKitView( viewType: 'plugins.flutter.io/webview', diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index 348b225bb257..b38d65acb486 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -25,31 +25,31 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { static const MethodChannel _cookieManagerChannel = MethodChannel('plugins.flutter.io/cookie_manager'); - Future _onMethodCall(MethodCall call) async { + Future _onMethodCall(MethodCall call) async { switch (call.method) { case 'javascriptChannelMessage': - final String channel = call.arguments['channel']; - final String message = call.arguments['message']; + final String channel = call.arguments['channel']!; + final String message = call.arguments['message']!; _platformCallbacksHandler.onJavaScriptChannelMessage(channel, message); return true; case 'navigationRequest': return await _platformCallbacksHandler.onNavigationRequest( - url: call.arguments['url'], - isForMainFrame: call.arguments['isForMainFrame'], + url: call.arguments['url']!, + isForMainFrame: call.arguments['isForMainFrame']!, ); case 'onPageFinished': - _platformCallbacksHandler.onPageFinished(call.arguments['url']); + _platformCallbacksHandler.onPageFinished(call.arguments['url']!); return null; case 'onPageStarted': - _platformCallbacksHandler.onPageStarted(call.arguments['url']); + _platformCallbacksHandler.onPageStarted(call.arguments['url']!); return null; case 'onWebResourceError': _platformCallbacksHandler.onWebResourceError( WebResourceError( - errorCode: call.arguments['errorCode'], - description: call.arguments['description'], + errorCode: call.arguments['errorCode']!, + description: call.arguments['description']!, + failingUrl: call.arguments['failingUrl']!, domain: call.arguments['domain'], - failingUrl: call.arguments['failingUrl'], errorType: call.arguments['errorType'] == null ? null : WebResourceErrorType.values.firstWhere( @@ -71,7 +71,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { @override Future loadUrl( String url, - Map headers, + Map? headers, ) async { assert(url != null); return _channel.invokeMethod('loadUrl', { @@ -81,13 +81,15 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { } @override - Future currentUrl() => _channel.invokeMethod('currentUrl'); + Future currentUrl() => _channel.invokeMethod('currentUrl'); @override - Future canGoBack() => _channel.invokeMethod("canGoBack"); + Future canGoBack() => + _channel.invokeMethod("canGoBack").then((result) => result!); @override - Future canGoForward() => _channel.invokeMethod("canGoForward"); + Future canGoForward() => + _channel.invokeMethod("canGoForward").then((result) => result!); @override Future goBack() => _channel.invokeMethod("goBack"); @@ -102,18 +104,18 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { Future clearCache() => _channel.invokeMethod("clearCache"); @override - Future updateSettings(WebSettings settings) { + Future updateSettings(WebSettings settings) async { final Map updatesMap = _webSettingsToMap(settings); - if (updatesMap.isEmpty) { - return null; + if (updatesMap.isNotEmpty) { + await _channel.invokeMethod('updateSettings', updatesMap); } - return _channel.invokeMethod('updateSettings', updatesMap); } @override Future evaluateJavascript(String javascriptString) { - return _channel.invokeMethod( - 'evaluateJavascript', javascriptString); + return _channel + .invokeMethod('evaluateJavascript', javascriptString) + .then((result) => result!); } @override @@ -129,7 +131,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { } @override - Future getTitle() => _channel.invokeMethod("getTitle"); + Future getTitle() => _channel.invokeMethod("getTitle"); @override Future scrollTo(int x, int y) { @@ -148,19 +150,21 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { } @override - Future getScrollX() => _channel.invokeMethod("getScrollX"); + Future getScrollX() => + _channel.invokeMethod("getScrollX").then((result) => result!); @override - Future getScrollY() => _channel.invokeMethod("getScrollY"); + Future getScrollY() => + _channel.invokeMethod("getScrollY").then((result) => result!); /// Method channel implementation for [WebViewPlatform.clearCookies]. static Future clearCookies() { return _cookieManagerChannel .invokeMethod('clearCookies') - .then((dynamic result) => result); + .then((dynamic result) => result!); } - static Map _webSettingsToMap(WebSettings settings) { + static Map _webSettingsToMap(WebSettings? settings) { final Map map = {}; void _addIfNonNull(String key, dynamic value) { if (value == null) { @@ -176,11 +180,13 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { map[key] = setting.value; } - _addIfNonNull('jsMode', settings.javascriptMode?.index); + _addIfNonNull('jsMode', settings!.javascriptMode?.index); _addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate); _addIfNonNull('debuggingEnabled', settings.debuggingEnabled); _addIfNonNull( 'gestureNavigationEnabled', settings.gestureNavigationEnabled); + _addIfNonNull( + 'allowsInlineMediaPlayback', settings.allowsInlineMediaPlayback); _addSettingIfPresent('userAgent', settings.userAgent); return map; } diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 2fdf639180a7..6853d39555c3 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -44,7 +44,7 @@ typedef void JavascriptMessageHandler(JavascriptMessage message); /// Information about a navigation action that is about to be executed. class NavigationRequest { - NavigationRequest._({this.url, this.isForMainFrame}); + NavigationRequest._({required this.url, required this.isForMainFrame}); /// The URL that will be loaded if the navigation is executed. final String url; @@ -79,11 +79,11 @@ enum NavigationDecision { class SurfaceAndroidWebView extends AndroidWebView { @override Widget build({ - BuildContext context, - CreationParams creationParams, - WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers, - @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + required BuildContext context, + required CreationParams creationParams, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, }) { assert(webViewPlatformCallbacksHandler != null); return PlatformViewLink( @@ -93,7 +93,7 @@ class SurfaceAndroidWebView extends AndroidWebView { PlatformViewController controller, ) { return AndroidViewSurface( - controller: controller, + controller: controller as AndroidViewController, gestureRecognizers: gestureRecognizers ?? const >{}, hitTestBehavior: PlatformViewHitTestBehavior.opaque, @@ -172,9 +172,9 @@ class JavascriptChannel { /// /// The parameters `name` and `onMessageReceived` must not be null. JavascriptChannel({ - @required this.name, - @required this.onMessageReceived, - }) : assert(name != null), + required this.name, + required this.onMessageReceived, + }) : assert(name != null), assert(onMessageReceived != null), assert(_validChannelNames.hasMatch(name)); @@ -208,7 +208,7 @@ class WebView extends StatefulWidget { /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. const WebView({ - Key key, + Key? key, this.onWebViewCreated, this.initialUrl, this.javascriptMode = JavascriptMode.disabled, @@ -223,11 +223,13 @@ class WebView extends StatefulWidget { this.userAgent, this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.allowsInlineMediaPlayback = false, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), + assert(allowsInlineMediaPlayback != null), super(key: key); - static WebViewPlatform _platform; + static WebViewPlatform? _platform; /// Sets a custom [WebViewPlatform]. /// @@ -236,7 +238,7 @@ class WebView extends StatefulWidget { /// 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) { + static set platform(WebViewPlatform? platform) { _platform = platform; } @@ -257,11 +259,11 @@ class WebView extends StatefulWidget { "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); } } - return _platform; + return _platform!; } /// If not null invoked once the web view is created. - final WebViewCreatedCallback onWebViewCreated; + final WebViewCreatedCallback? onWebViewCreated; /// Which gestures should be consumed by the web view. /// @@ -272,10 +274,10 @@ class WebView extends StatefulWidget { /// /// 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; + final Set>? gestureRecognizers; /// The initial URL to load. - final String initialUrl; + final String? initialUrl; /// Whether Javascript execution is enabled. final JavascriptMode javascriptMode; @@ -307,7 +309,7 @@ class WebView extends StatefulWidget { /// channels in the list. /// /// A null value is equivalent to an empty set. - final Set javascriptChannels; + final Set? javascriptChannels; /// A delegate function that decides how to handle navigation actions. /// @@ -331,10 +333,17 @@ class WebView extends StatefulWidget { /// * 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; + 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; + final PageStartedCallback? onPageStarted; /// Invoked when a page has finished loading. /// @@ -346,13 +355,13 @@ class WebView extends StatefulWidget { /// 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; + final PageFinishedCallback? onPageFinished; /// Invoked when a web resource has failed to load. /// /// This can be called for any resource (iframe, image, etc.), not just for /// the main page. - final WebResourceErrorCallback onWebResourceError; + final WebResourceErrorCallback? onWebResourceError; /// Controls whether WebView debugging is enabled. /// @@ -386,7 +395,7 @@ class WebView extends StatefulWidget { /// user agent. /// /// By default `userAgent` is null. - final String userAgent; + final String? userAgent; /// Which restrictions apply on automatic media playback. /// @@ -404,7 +413,7 @@ class _WebViewState extends State { final Completer _controller = Completer(); - _PlatformCallbacksHandler _platformCallbacksHandler; + late _PlatformCallbacksHandler _platformCallbacksHandler; @override Widget build(BuildContext context) { @@ -434,22 +443,22 @@ class _WebViewState extends State { }); } - void _onWebViewPlatformCreated(WebViewPlatformController webViewPlatform) { - final WebViewController controller = - WebViewController._(widget, webViewPlatform, _platformCallbacksHandler); + void _onWebViewPlatformCreated(WebViewPlatformController? webViewPlatform) { + final WebViewController controller = WebViewController._( + widget, webViewPlatform!, _platformCallbacksHandler); _controller.complete(controller); if (widget.onWebViewCreated != null) { - widget.onWebViewCreated(controller); + widget.onWebViewCreated!(controller); } } void _assertJavascriptChannelNamesAreUnique() { if (widget.javascriptChannels == null || - widget.javascriptChannels.isEmpty) { + widget.javascriptChannels!.isEmpty) { return; } assert(_extractChannelNames(widget.javascriptChannels).length == - widget.javascriptChannels.length); + widget.javascriptChannels!.length); } } @@ -469,7 +478,8 @@ WebSettings _webSettingsFromWidget(WebView widget) { hasNavigationDelegate: widget.navigationDelegate != null, debuggingEnabled: widget.debuggingEnabled, gestureNavigationEnabled: widget.gestureNavigationEnabled, - userAgent: WebSetting.of(widget.userAgent), + allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, + userAgent: WebSetting.of(widget.userAgent), ); } @@ -479,16 +489,16 @@ WebSettings _clearUnchangedWebSettings( assert(currentValue.javascriptMode != null); assert(currentValue.hasNavigationDelegate != null); assert(currentValue.debuggingEnabled != null); - assert(currentValue.userAgent.isPresent); + assert(currentValue.userAgent != null); assert(newValue.javascriptMode != null); assert(newValue.hasNavigationDelegate != null); assert(newValue.debuggingEnabled != null); - assert(newValue.userAgent.isPresent); + assert(newValue.userAgent != null); - JavascriptMode javascriptMode; - bool hasNavigationDelegate; - bool debuggingEnabled; - WebSetting userAgent = WebSetting.absent(); + JavascriptMode? javascriptMode; + bool? hasNavigationDelegate; + bool? debuggingEnabled; + WebSetting userAgent = WebSetting.absent(); if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } @@ -510,7 +520,7 @@ WebSettings _clearUnchangedWebSettings( ); } -Set _extractChannelNames(Set channels) { +Set _extractChannelNames(Set? channels) { final Set channelNames = channels == null ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); @@ -530,15 +540,18 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { @override void onJavaScriptChannelMessage(String channel, String message) { - _javascriptChannels[channel].onMessageReceived(JavascriptMessage(message)); + _javascriptChannels[channel]!.onMessageReceived(JavascriptMessage(message)); } @override - FutureOr onNavigationRequest({String url, bool isForMainFrame}) async { + 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) == + await _widget.navigationDelegate!(request) == NavigationDecision.navigate; return allowNavigation; } @@ -546,25 +559,25 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { @override void onPageStarted(String url) { if (_widget.onPageStarted != null) { - _widget.onPageStarted(url); + _widget.onPageStarted!(url); } } @override void onPageFinished(String url) { if (_widget.onPageFinished != null) { - _widget.onPageFinished(url); + _widget.onPageFinished!(url); } } @override void onWebResourceError(WebResourceError error) { if (_widget.onWebResourceError != null) { - _widget.onWebResourceError(error); + _widget.onWebResourceError!(error); } } - void _updateJavascriptChannelsFromSet(Set channels) { + void _updateJavascriptChannelsFromSet(Set? channels) { _javascriptChannels.clear(); if (channels == null) { return; @@ -592,7 +605,7 @@ class WebViewController { final _PlatformCallbacksHandler _platformCallbacksHandler; - WebSettings _settings; + late WebSettings _settings; WebView _widget; @@ -606,7 +619,7 @@ class WebViewController { /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, { - Map headers, + Map? headers, }) async { assert(url != null); _validateUrlString(url); @@ -620,7 +633,7 @@ class WebViewController { /// 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() { + Future currentUrl() { return _webViewPlatformController.currentUrl(); } @@ -688,7 +701,7 @@ class WebViewController { } Future _updateJavascriptChannels( - Set newChannels) async { + Set? newChannels) async { final Set currentChannels = _platformCallbacksHandler._javascriptChannels.keys.toSet(); final Set newChannelNames = _extractChannelNames(newChannels); @@ -727,10 +740,6 @@ class WebViewController { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); } - if (javascriptString == null) { - return Future.error( - ArgumentError('The argument javascriptString must not be null.')); - } // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. // https://github.com/flutter/flutter/issues/26431 // ignore: strong_mode_implicit_dynamic_method @@ -738,7 +747,7 @@ class WebViewController { } /// Returns the title of the currently loaded page. - Future getTitle() { + Future getTitle() { return _webViewPlatformController.getTitle(); } @@ -780,7 +789,7 @@ class CookieManager { CookieManager._(); - static CookieManager _instance; + static CookieManager? _instance; /// Clears all cookies for all [WebView] instances. /// diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index e4627a4f9f65..b2fbfc1110c5 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,11 +1,11 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 1.0.7 +version: 2.0.0-nullsafety.2 homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.22.0 <2.0.0" + sdk: ">=2.12.0-0 <3.0.0" + flutter: ">=1.22.0" dependencies: flutter: @@ -16,7 +16,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - pedantic: ^1.8.0 + pedantic: ^1.10.0-nullsafety.1 flutter: plugin: diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart index c7cf46a080d7..162b1932e49d 100644 --- a/packages/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/test/webview_flutter_test.dart @@ -40,7 +40,7 @@ void main() { }); testWidgets('Initial url', (WidgetTester tester) async { - WebViewController controller; + late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', @@ -60,7 +60,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.javascriptMode, JavascriptMode.unrestricted); @@ -72,7 +72,7 @@ void main() { }); testWidgets('Load url', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -83,13 +83,13 @@ void main() { expect(controller, isNotNull); - await controller.loadUrl('https://flutter.io'); + await controller!.loadUrl('https://flutter.io'); - expect(await controller.currentUrl(), 'https://flutter.io'); + expect(await controller!.currentUrl(), 'https://flutter.io'); }); testWidgets('Invalid urls', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -100,19 +100,18 @@ void main() { expect(controller, isNotNull); - expect(() => controller.loadUrl(null), throwsA(anything)); - expect(await controller.currentUrl(), isNull); + expect(await controller!.currentUrl(), isNull); - expect(() => controller.loadUrl(''), throwsA(anything)); - expect(await controller.currentUrl(), isNull); + expect(() => controller!.loadUrl(''), throwsA(anything)); + expect(await controller!.currentUrl(), isNull); // Missing schema. - expect(() => controller.loadUrl('flutter.io'), throwsA(anything)); - expect(await controller.currentUrl(), isNull); + expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); + expect(await controller!.currentUrl(), isNull); }); testWidgets('Headers in loadUrl', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -126,13 +125,13 @@ void main() { final Map headers = { 'CACHE-CONTROL': 'ABC' }; - await controller.loadUrl('https://flutter.io', headers: headers); - expect(await controller.currentUrl(), equals('https://flutter.io')); + await controller!.loadUrl('https://flutter.io', headers: headers); + expect(await controller!.currentUrl(), equals('https://flutter.io')); }); testWidgets("Can't go back before loading a page", (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -143,13 +142,13 @@ void main() { expect(controller, isNotNull); - final bool canGoBackNoPageLoaded = await controller.canGoBack(); + final bool canGoBackNoPageLoaded = await controller!.canGoBack(); expect(canGoBackNoPageLoaded, false); }); testWidgets("Clear Cache", (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -159,15 +158,15 @@ void main() { ); expect(controller, isNotNull); - expect(fakePlatformViewsController.lastCreatedView.hasCache, true); + expect(fakePlatformViewsController.lastCreatedView!.hasCache, true); - await controller.clearCache(); + await controller!.clearCache(); - expect(fakePlatformViewsController.lastCreatedView.hasCache, false); + expect(fakePlatformViewsController.lastCreatedView!.hasCache, false); }); testWidgets("Can't go back with no history", (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -178,13 +177,13 @@ void main() { ); expect(controller, isNotNull); - final bool canGoBackFirstPageLoaded = await controller.canGoBack(); + final bool canGoBackFirstPageLoaded = await controller!.canGoBack(); expect(canGoBackFirstPageLoaded, false); }); testWidgets('Can go back', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -196,15 +195,15 @@ void main() { expect(controller, isNotNull); - await controller.loadUrl('https://www.google.com'); - final bool canGoBackSecondPageLoaded = await controller.canGoBack(); + await controller!.loadUrl('https://www.google.com'); + final bool canGoBackSecondPageLoaded = await controller!.canGoBack(); expect(canGoBackSecondPageLoaded, true); }); testWidgets("Can't go forward before loading a page", (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -215,13 +214,13 @@ void main() { expect(controller, isNotNull); - final bool canGoForwardNoPageLoaded = await controller.canGoForward(); + final bool canGoForwardNoPageLoaded = await controller!.canGoForward(); expect(canGoForwardNoPageLoaded, false); }); testWidgets("Can't go forward with no history", (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -232,13 +231,13 @@ void main() { ); expect(controller, isNotNull); - final bool canGoForwardFirstPageLoaded = await controller.canGoForward(); + final bool canGoForwardFirstPageLoaded = await controller!.canGoForward(); expect(canGoForwardFirstPageLoaded, false); }); testWidgets('Can go forward', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -250,15 +249,15 @@ void main() { expect(controller, isNotNull); - await controller.loadUrl('https://youtube.com'); - await controller.goBack(); - final bool canGoForwardFirstPageBacked = await controller.canGoForward(); + await controller!.loadUrl('https://youtube.com'); + await controller!.goBack(); + final bool canGoForwardFirstPageBacked = await controller!.canGoForward(); expect(canGoForwardFirstPageBacked, true); }); testWidgets('Go back', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', @@ -270,19 +269,19 @@ void main() { expect(controller, isNotNull); - expect(await controller.currentUrl(), 'https://youtube.com'); + expect(await controller!.currentUrl(), 'https://youtube.com'); - await controller.loadUrl('https://flutter.io'); + await controller!.loadUrl('https://flutter.io'); - expect(await controller.currentUrl(), 'https://flutter.io'); + expect(await controller!.currentUrl(), 'https://flutter.io'); - await controller.goBack(); + await controller!.goBack(); - expect(await controller.currentUrl(), 'https://youtube.com'); + expect(await controller!.currentUrl(), 'https://youtube.com'); }); testWidgets('Go forward', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', @@ -294,23 +293,23 @@ void main() { expect(controller, isNotNull); - expect(await controller.currentUrl(), 'https://youtube.com'); + expect(await controller!.currentUrl(), 'https://youtube.com'); - await controller.loadUrl('https://flutter.io'); + await controller!.loadUrl('https://flutter.io'); - expect(await controller.currentUrl(), 'https://flutter.io'); + expect(await controller!.currentUrl(), 'https://flutter.io'); - await controller.goBack(); + await controller!.goBack(); - expect(await controller.currentUrl(), 'https://youtube.com'); + expect(await controller!.currentUrl(), 'https://youtube.com'); - await controller.goForward(); + await controller!.goForward(); - expect(await controller.currentUrl(), 'https://flutter.io'); + expect(await controller!.currentUrl(), 'https://flutter.io'); }); testWidgets('Current URL', (WidgetTester tester) async { - WebViewController controller; + WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { @@ -322,20 +321,20 @@ void main() { expect(controller, isNotNull); // Test a WebView without an explicitly set first URL. - expect(await controller.currentUrl(), isNull); + expect(await controller!.currentUrl(), isNull); - await controller.loadUrl('https://youtube.com'); - expect(await controller.currentUrl(), 'https://youtube.com'); + await controller!.loadUrl('https://youtube.com'); + expect(await controller!.currentUrl(), 'https://youtube.com'); - await controller.loadUrl('https://flutter.io'); - expect(await controller.currentUrl(), 'https://flutter.io'); + await controller!.loadUrl('https://flutter.io'); + expect(await controller!.currentUrl(), 'https://flutter.io'); - await controller.goBack(); - expect(await controller.currentUrl(), 'https://youtube.com'); + await controller!.goBack(); + expect(await controller!.currentUrl(), 'https://youtube.com'); }); testWidgets('Reload url', (WidgetTester tester) async { - WebViewController controller; + late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -346,7 +345,7 @@ void main() { ); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.currentUrl, 'https://flutter.io'); expect(platformWebView.amountOfReloadsOnCurrentUrl, 0); @@ -362,7 +361,7 @@ void main() { }); testWidgets('evaluate Javascript', (WidgetTester tester) async { - WebViewController controller; + late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -375,15 +374,11 @@ void main() { expect( await controller.evaluateJavascript("fake js string"), "fake js string", reason: 'should get the argument'); - expect( - () => controller.evaluateJavascript(null), - throwsA(anything), - ); }); testWidgets('evaluate Javascript with JavascriptMode disabled', (WidgetTester tester) async { - WebViewController controller; + late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', @@ -397,10 +392,6 @@ void main() { () => controller.evaluateJavascript('fake js string'), throwsA(anything), ); - expect( - () => controller.evaluateJavascript(null), - throwsA(anything), - ); }); testWidgets('Cookies can be cleared once', (WidgetTester tester) async { @@ -432,19 +423,17 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts', 'Alarm'])); @@ -472,14 +461,12 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); expect(tester.takeException(), isNot(null)); @@ -489,35 +476,31 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts', 'Alarm2', 'Alarm3'])); @@ -532,12 +515,10 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); @@ -550,17 +531,15 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), - ].toSet(), + }, ), ); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.javascriptChannelNames, unorderedEquals(['Tts'])); @@ -572,9 +551,7 @@ void main() { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannels: [ + javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) { @@ -585,12 +562,12 @@ void main() { onMessageReceived: (JavascriptMessage msg) { alarmMessagesReceived.add(msg.message); }), - ].toSet(), + }, ), ); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(ttsMessagesReceived, isEmpty); expect(alarmMessagesReceived, isEmpty); @@ -603,7 +580,7 @@ void main() { group('$PageStartedCallback', () { testWidgets('onPageStarted is not null', (WidgetTester tester) async { - String returnedUrl; + String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', @@ -613,7 +590,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; platformWebView.fakeOnPageStartedCallback(); @@ -627,7 +604,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; // The platform side will always invoke a call for onPageStarted. This is // to test that it does not crash on a null callback. @@ -635,7 +612,7 @@ void main() { }); testWidgets('onPageStarted changed', (WidgetTester tester) async { - String returnedUrl; + String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', @@ -650,7 +627,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; platformWebView.fakeOnPageStartedCallback(); @@ -660,7 +637,7 @@ void main() { group('$PageFinishedCallback', () { testWidgets('onPageFinished is not null', (WidgetTester tester) async { - String returnedUrl; + String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', @@ -670,7 +647,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; platformWebView.fakeOnPageFinishedCallback(); @@ -684,7 +661,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; // The platform side will always invoke a call for onPageFinished. This is // to test that it does not crash on a null callback. @@ -692,7 +669,7 @@ void main() { }); testWidgets('onPageFinished changed', (WidgetTester tester) async { - String returnedUrl; + String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', @@ -707,7 +684,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; platformWebView.fakeOnPageFinishedCallback(); @@ -722,13 +699,14 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.hasNavigationDelegate, false); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', - navigationDelegate: (NavigationRequest r) => null, + navigationDelegate: (NavigationRequest r) => + NavigationDecision.navigate, )); expect(platformWebView.hasNavigationDelegate, true); @@ -748,7 +726,7 @@ void main() { })); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.hasNavigationDelegate, true); @@ -773,7 +751,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.debuggingEnabled, true); }); @@ -782,7 +760,7 @@ void main() { await tester.pumpWidget(const WebView()); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.debuggingEnabled, false); }); @@ -792,7 +770,7 @@ void main() { await tester.pumpWidget(WebView(key: key)); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; await tester.pumpWidget(WebView( key: key, @@ -826,8 +804,8 @@ void main() { ), ); - final MyWebViewPlatform builder = WebView.platform; - final MyWebViewPlatformController platform = builder.lastPlatformBuilt; + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; expect( platform.creationParams, @@ -837,17 +815,14 @@ void main() { javascriptMode: JavascriptMode.disabled, hasNavigationDelegate: false, debuggingEnabled: false, - userAgent: WebSetting.of(null), + userAgent: WebSetting.of(null), gestureNavigationEnabled: true, ), - // TODO(iskakaushik): Remove this when collection literals makes it to stable. - // ignore: prefer_collection_literals - javascriptChannelNames: Set(), ))); }); testWidgets('loadUrl', (WidgetTester tester) async { - WebViewController controller; + late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', @@ -857,8 +832,8 @@ void main() { ), ); - final MyWebViewPlatform builder = WebView.platform; - final MyWebViewPlatformController platform = builder.lastPlatformBuilt; + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; final Map headers = { 'header': 'value', @@ -877,7 +852,7 @@ void main() { )); final FakePlatformWebView platformWebView = - fakePlatformViewsController.lastCreatedView; + fakePlatformViewsController.lastCreatedView!; expect(platformWebView.userAgent, isNull); @@ -892,9 +867,9 @@ void main() { } class FakePlatformWebView { - FakePlatformWebView(int id, Map params) { + FakePlatformWebView(int? id, Map params) { if (params.containsKey('initialUrl')) { - final String initialUrl = params['initialUrl']; + final String? initialUrl = params['initialUrl']; if (initialUrl != null) { history.add(initialUrl); currentPosition++; @@ -914,20 +889,20 @@ class FakePlatformWebView { channel.setMockMethodCallHandler(onMethodCall); } - MethodChannel channel; + late MethodChannel channel; - List history = []; + List history = []; int currentPosition = -1; int amountOfReloadsOnCurrentUrl = 0; bool hasCache = true; - String get currentUrl => history.isEmpty ? null : history[currentPosition]; - JavascriptMode javascriptMode; - List javascriptChannelNames; + String? get currentUrl => history.isEmpty ? null : history[currentPosition]; + JavascriptMode? javascriptMode; + List? javascriptChannelNames; - bool hasNavigationDelegate; - bool debuggingEnabled; - String userAgent; + bool? hasNavigationDelegate; + bool? debuggingEnabled; + String? userAgent; Future onMethodCall(MethodCall call) { switch (call.method) { @@ -949,34 +924,28 @@ class FakePlatformWebView { break; case 'canGoBack': return Future.sync(() => currentPosition > 0); - break; case 'canGoForward': return Future.sync(() => currentPosition < history.length - 1); - break; case 'goBack': currentPosition = max(-1, currentPosition - 1); return Future.sync(() {}); - break; case 'goForward': currentPosition = min(history.length - 1, currentPosition + 1); return Future.sync(() {}); case 'reload': amountOfReloadsOnCurrentUrl++; return Future.sync(() {}); - break; case 'currentUrl': - return Future.value(currentUrl); - break; + return Future.value(currentUrl); case 'evaluateJavascript': return Future.value(call.arguments); - break; case 'addJavascriptChannels': final List channelNames = List.from(call.arguments); - javascriptChannelNames.addAll(channelNames); + javascriptChannelNames!.addAll(channelNames); break; case 'removeJavascriptChannels': final List channelNames = List.from(call.arguments); - javascriptChannelNames + javascriptChannelNames! .removeWhere((String channel) => channelNames.contains(channel)); break; case 'clearCache': @@ -994,14 +963,14 @@ class FakePlatformWebView { }; final ByteData data = codec .encodeMethodCall(MethodCall('javascriptChannelMessage', arguments)); - ServicesBinding.instance.defaultBinaryMessenger - .handlePlatformMessage(channel.name, data, (ByteData data) {}); + ServicesBinding.instance!.defaultBinaryMessenger + .handlePlatformMessage(channel.name, data, (ByteData? data) {}); } // Fakes a main frame navigation that was initiated by the webview, e.g when // the user clicks a link in the currently loaded page. void fakeNavigate(String url) { - if (!hasNavigationDelegate) { + if (!hasNavigationDelegate!) { print('no navigation delegate'); _loadUrl(url); return; @@ -1013,9 +982,9 @@ class FakePlatformWebView { }; final ByteData data = codec.encodeMethodCall(MethodCall('navigationRequest', arguments)); - ServicesBinding.instance.defaultBinaryMessenger - .handlePlatformMessage(channel.name, data, (ByteData data) { - final bool allow = codec.decodeEnvelope(data); + ServicesBinding.instance!.defaultBinaryMessenger + .handlePlatformMessage(channel.name, data, (ByteData? data) { + final bool allow = codec.decodeEnvelope(data!); if (allow) { _loadUrl(url); } @@ -1030,10 +999,10 @@ class FakePlatformWebView { {'url': currentUrl}, )); - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( channel.name, data, - (ByteData data) {}, + (ByteData? data) {}, ); } @@ -1045,14 +1014,14 @@ class FakePlatformWebView { {'url': currentUrl}, )); - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( channel.name, data, - (ByteData data) {}, + (ByteData? data) {}, ); } - void _loadUrl(String url) { + void _loadUrl(String? url) { history = history.sublist(0, currentPosition + 1); history.add(url); currentPosition++; @@ -1061,13 +1030,13 @@ class FakePlatformWebView { } class _FakePlatformViewsController { - FakePlatformWebView lastCreatedView; + FakePlatformWebView? lastCreatedView; Future fakePlatformViewsMethodHandler(MethodCall call) { switch (call.method) { case 'create': final Map args = call.arguments; - final Map params = _decodeParams(args['params']); + final Map params = _decodeParams(args['params'])!; lastCreatedView = FakePlatformWebView( args['id'], params, @@ -1083,7 +1052,7 @@ class _FakePlatformViewsController { } } -Map _decodeParams(Uint8List paramsMessage) { +Map? _decodeParams(Uint8List paramsMessage) { final ByteBuffer buffer = paramsMessage.buffer; final ByteData messageBytes = buffer.asByteData( paramsMessage.offsetInBytes, @@ -1114,9 +1083,8 @@ class _FakeCookieManager { return Future.sync(() { return hadCookies; }); - break; } - return Future.sync(() => null); + return Future.sync(() => true); } void reset() { @@ -1125,26 +1093,26 @@ class _FakeCookieManager { } class MyWebViewPlatform implements WebViewPlatform { - MyWebViewPlatformController lastPlatformBuilt; + MyWebViewPlatformController? lastPlatformBuilt; @override Widget build({ - BuildContext context, - CreationParams creationParams, - @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, - @required WebViewPlatformCreatedCallback onWebViewPlatformCreated, - Set> gestureRecognizers, + BuildContext? context, + CreationParams? creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, }) { assert(onWebViewPlatformCreated != null); lastPlatformBuilt = MyWebViewPlatformController( creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); - onWebViewPlatformCreated(lastPlatformBuilt); + onWebViewPlatformCreated!(lastPlatformBuilt); return Container(); } @override Future clearCookies() { - return Future.sync(() => null); + return Future.sync(() => true); } } @@ -1153,25 +1121,24 @@ class MyWebViewPlatformController extends WebViewPlatformController { WebViewPlatformCallbacksHandler platformHandler) : super(platformHandler); - CreationParams creationParams; - Set> gestureRecognizers; + CreationParams? creationParams; + Set>? gestureRecognizers; - String lastUrlLoaded; - Map lastRequestHeaders; + String? lastUrlLoaded; + Map? lastRequestHeaders; @override - Future loadUrl(String url, Map headers) { + Future loadUrl(String url, Map? headers) async { equals(1, 1); lastUrlLoaded = url; lastRequestHeaders = headers; - return null; } } class MatchesWebSettings extends Matcher { MatchesWebSettings(this._webSettings); - final WebSettings _webSettings; + final WebSettings? _webSettings; @override Description describe(Description description) => @@ -1180,13 +1147,13 @@ class MatchesWebSettings extends Matcher { @override bool matches( covariant WebSettings webSettings, Map matchState) { - return _webSettings.javascriptMode == webSettings.javascriptMode && - _webSettings.hasNavigationDelegate == + return _webSettings!.javascriptMode == webSettings.javascriptMode && + _webSettings!.hasNavigationDelegate == webSettings.hasNavigationDelegate && - _webSettings.debuggingEnabled == webSettings.debuggingEnabled && - _webSettings.gestureNavigationEnabled == + _webSettings!.debuggingEnabled == webSettings.debuggingEnabled && + _webSettings!.gestureNavigationEnabled == webSettings.gestureNavigationEnabled && - _webSettings.userAgent == webSettings.userAgent; + _webSettings!.userAgent == webSettings.userAgent; } } @@ -1204,7 +1171,7 @@ class MatchesCreationParams extends Matcher { Map matchState) { return _creationParams.initialUrl == creationParams.initialUrl && MatchesWebSettings(_creationParams.webSettings) - .matches(creationParams.webSettings, matchState) && + .matches(creationParams.webSettings!, matchState) && orderedEquals(_creationParams.javascriptChannelNames) .matches(creationParams.javascriptChannelNames, matchState); } diff --git a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md index 37711dcac49c..5c3ea32e8ef9 100644 --- a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md +++ b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.3 + +* Fix README example. + +## 1.0.2 + +* Update Flutter SDK constraint. + ## 1.0.1 * Fixed method channel name in android implementation. [Issue](https://github.com/flutter/flutter/issues/69073). diff --git a/packages/wifi_info_flutter/wifi_info_flutter/README.md b/packages/wifi_info_flutter/wifi_info_flutter/README.md index 0c2eff383c16..e1c0c84dc04b 100644 --- a/packages/wifi_info_flutter/wifi_info_flutter/README.md +++ b/packages/wifi_info_flutter/wifi_info_flutter/README.md @@ -24,9 +24,9 @@ You can get wi-fi related information using: ```dart import 'package:wifi_info_flutter/wifi_info_flutter.dart'; -var wifiBSSID = await WifiFlutter().getWifiBSSID(); -var wifiIP = await WifiFlutter().getWifiIP(); -var wifiName = await WifiFlutter().getWifiName(); +var wifiBSSID = await WifiInfo().getWifiBSSID(); +var wifiIP = await WifiInfo().getWifiIP(); +var wifiName = await WifiInfo().getWifiName(); ``` ### iOS 12 diff --git a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml index 74ee9f074cb5..4f5ecafb50b4 100644 --- a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml +++ b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml @@ -1,11 +1,11 @@ name: wifi_info_flutter description: A new flutter plugin project. -version: 1.0.1 +version: 1.0.3 homepage: https://github.com/flutter/plugins/tree/master/packages/wifi_info_flutter/wifi_info_flutter environment: sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.20.0 <2.0.0" + flutter: ">=1.20.0" dependencies: flutter: diff --git a/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/CHANGELOG.md b/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/CHANGELOG.md index a845f1956ab4..951f8f5f5ae0 100644 --- a/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/CHANGELOG.md +++ b/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +* Update Flutter SDK constraint. + ## 1.0.0 * Initial release of package. Includes support for retrieving wifi name, wifi BSSID, wifi ip address diff --git a/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/pubspec.yaml b/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/pubspec.yaml index 68d516d515a8..62bffd0eeeb3 100644 --- a/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/pubspec.yaml +++ b/packages/wifi_info_flutter/wifi_info_flutter_platform_interface/pubspec.yaml @@ -1,13 +1,13 @@ name: wifi_info_flutter_platform_interface description: A common platform interface for the wifi_info_flutter plugin. -version: 1.0.0 +version: 1.0.1 # 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 homepage: https://github.com/flutter/plugins/tree/master/packages/wifi_info_flutter/wifi_info_flutter_platform_interface environment: sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0 <2.0.0" + flutter: ">=1.17.0" dependencies: plugin_platform_interface: ^1.0.3 diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index f1f68ddbe985..f2897f8aa9a2 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -8,6 +8,8 @@ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)" readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" source "$SCRIPT_DIR/common.sh" +source "$SCRIPT_DIR/nnbd_plugins.sh" + check_changed_packages > /dev/null readonly EXCLUDED_PLUGINS_LIST=( @@ -42,7 +44,15 @@ readonly EXCLUDED_PLUGINS_LIST=( # Comma-separated string of the list above readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}") -(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude $EXCLUDED) +ALL_EXCLUDED=($EXCLUDED) +# Exclude nnbd plugins from stable. +if [[ "$CHANNEL" -eq "stable" ]]; then + ALL_EXCLUDED=("$EXCLUDED,$EXCLUDED_PLUGINS_FROM_STABLE") +fi + +echo "Excluding the following plugins: $ALL_EXCLUDED" + +(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude $ALL_EXCLUDED) function error() { echo "$@" 1>&2 diff --git a/script/incremental_build.sh b/script/incremental_build.sh index f89bc1d0e5c9..f54f90b0669c 100755 --- a/script/incremental_build.sh +++ b/script/incremental_build.sh @@ -5,6 +5,7 @@ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" source "$SCRIPT_DIR/common.sh" +source "$SCRIPT_DIR/nnbd_plugins.sh" if [ "$(expr substr $(uname -s) 1 5)" == "MINGW" ]; then PUB=pub.bat @@ -12,6 +13,14 @@ else PUB=pub fi +# Plugins that are excluded from this task. +ALL_EXCLUDED=("") +# Exclude nnbd plugins from stable. +if [[ "$CHANNEL" -eq "stable" ]]; then + ALL_EXCLUDED=($EXCLUDED_PLUGINS_FROM_STABLE) + echo "Excluding the following plugins: $ALL_EXCLUDED" +fi + # Plugins that deliberately use their own analysis_options.yaml. # # This list should only be deleted from, never added to. This only exists @@ -21,6 +30,7 @@ fi # TODO(mklim): Remove everything from this list. https://github.com/flutter/flutter/issues/45440 CUSTOM_ANALYSIS_PLUGINS=( ) + # Comma-separated string of the list above readonly CUSTOM_FLAG=$(IFS=, ; echo "${CUSTOM_ANALYSIS_PLUGINS[*]}") # Set some default actions if run without arguments. @@ -39,7 +49,7 @@ PLUGIN_SHARDING=($PLUGIN_SHARDING) if [[ "${BRANCH_NAME}" == "master" ]]; then echo "Running for all packages" - (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" ${PLUGIN_SHARDING[@]}) + (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" --exclude="$ALL_EXCLUDED" ${PLUGIN_SHARDING[@]}) else # Sets CHANGED_PACKAGES check_changed_packages @@ -47,11 +57,12 @@ else if [[ "$CHANGED_PACKAGES" == "" ]]; then echo "No changes detected in packages." echo "Running for all packages" - (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" ${PLUGIN_SHARDING[@]}) + (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" --exclude="$ALL_EXCLUDED" ${PLUGIN_SHARDING[@]}) else echo running "${ACTIONS[@]}" - (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" --plugins="$CHANGED_PACKAGES" ${PLUGIN_SHARDING[@]}) + (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools "${ACTIONS[@]}" --plugins="$CHANGED_PACKAGES" --exclude="$ALL_EXCLUDED" ${PLUGIN_SHARDING[@]}) echo "Running version check for changed packages" - (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools version-check --base_sha="$(get_branch_base_sha)") + # TODO(egarciad): Enable this check once in master. + # (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools version-check --base_sha="$(get_branch_base_sha)") fi fi diff --git a/script/nnbd_plugins.sh b/script/nnbd_plugins.sh new file mode 100644 index 000000000000..0cab28abe2ab --- /dev/null +++ b/script/nnbd_plugins.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# This script contains the list of plugins migrated to nnbd +# that should be excluded from testing on Flutter stable until +# null-safe is available on stable. + +readonly NNBD_PLUGINS_LIST=( + "android_intent" + "connectivity" + "device_info" + "flutter_plugin_android_lifecycle" + "flutter_webview" + "google_sign_in" + "local_auth" + "path_provider" + "plugin_platform_interface" + "share" + "url_launcher" + "video_player" + "webview_flutter" +) + +export EXCLUDED_PLUGINS_FROM_STABLE=$(IFS=, ; echo "${NNBD_PLUGINS_LIST[*]}")