diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index de180c5decf..d4cdb036c56 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -0e536eb9fe4b15eb22ea62c0652f0851b9580026 +8b18dde77fa59ba7f87540c05d1aba787198e77a diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ffb540b6587..effa6ef6379 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.6.18+2 + +* Fixes premature garbage collection of native objects when app is under memory pressure. + +## 0.6.18+1 + +* Makes Java style improvements. + ## 0.6.18 * Adds support for the `MediaSettings.enableAudio` setting, which determines whether or not audio is diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index 4ed1e856598..3cf1451a6bb 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -49,6 +49,11 @@ android { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { + // The org.gradle.jvmargs property that may be set in gradle.properties does not impact + // the Java heap size when running the Android unit tests. The following property here + // sets the heap size to a size large enough to run the robolectric tests across + // multiple SDK levels. + jvmArgs "-Xmx1G" testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} diff --git a/packages/camera/camera_android_camerax/android/lint-baseline.xml b/packages/camera/camera_android_camerax/android/lint-baseline.xml index 7a4999067f3..dcfe3c836cf 100644 --- a/packages/camera/camera_android_camerax/android/lint-baseline.xml +++ b/packages/camera/camera_android_camerax/android/lint-baseline.xml @@ -1,125 +1,125 @@ - + + errorLine1=" } else if (value is androidx.camera.camera2.interop.CaptureRequestOptions) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="937" + column="25"/> + errorLine1=" } else if (value is androidx.camera.camera2.interop.Camera2CameraControl) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="939" + column="25"/> + errorLine1=" } else if (value is androidx.camera.camera2.interop.Camera2CameraInfo) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="947" + column="25"/> + errorLine1=" ): androidx.camera.camera2.interop.CaptureRequestOptions" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6418" + column="6"/> + errorLine1=" pigeon_instance: androidx.camera.camera2.interop.CaptureRequestOptions," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6422" + column="24"/> + errorLine1=" args[0] as androidx.camera.camera2.interop.CaptureRequestOptions" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6468" + column="28"/> + errorLine1=" pigeon_instanceArg: androidx.camera.camera2.interop.CaptureRequestOptions," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6488" + column="27"/> + errorLine1=" ): androidx.camera.camera2.interop.Camera2CameraControl" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6533" + column="6"/> + errorLine1=" pigeon_instance: androidx.camera.camera2.interop.Camera2CameraControl," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6537" + column="24"/> + errorLine1=" bundle: androidx.camera.camera2.interop.CaptureRequestOptions," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6538" + column="15"/> @@ -140,7 +140,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -151,7 +151,7 @@ errorLine2=" ~~~~~~~~~"> @@ -162,52 +162,52 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" pigeon_instanceArg: androidx.camera.camera2.interop.Camera2CameraControl," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6604" + column="27"/> + errorLine1=" ): androidx.camera.camera2.interop.Camera2CameraInfo" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6899" + column="6"/> + errorLine1=" pigeon_instance: androidx.camera.camera2.interop.Camera2CameraInfo" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6903" + column="24"/> + errorLine1=" pigeon_instance: androidx.camera.camera2.interop.Camera2CameraInfo," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6908" + column="24"/> @@ -228,7 +228,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -239,7 +239,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~"> @@ -250,19 +250,19 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" pigeon_instanceArg: androidx.camera.camera2.interop.Camera2CameraInfo," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="6992" + column="27"/> diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerProxyApi.java index 61a217393fd..ad4b19d089f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerProxyApi.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.camera.core.ImageAnalysis.Analyzer; +import androidx.camera.core.ImageProxy; import java.util.Objects; /** @@ -33,7 +34,7 @@ static class AnalyzerImpl implements Analyzer { } @Override - public void analyze(@NonNull androidx.camera.core.ImageProxy image) { + public void analyze(@NonNull ImageProxy image) { api.getPigeonRegistrar() .runOnMainThread( new ProxyApiRegistrar.FlutterMethodRunnable() { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyProxyApi.java index 942c2682ecb..b7c00298037 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyProxyApi.java @@ -32,6 +32,11 @@ public AspectRatioStrategy pigeon_defaultConstructor( break; case RATIO_DEFAULT: nativeAspectRatio = androidx.camera.core.AspectRatio.RATIO_DEFAULT; + break; + case UNKNOWN: + default: + // Default to nativeAspectRatio -2. + break; } int nativeFallbackRule = -1; switch (fallbackRule) { @@ -41,6 +46,10 @@ public AspectRatioStrategy pigeon_defaultConstructor( case NONE: nativeFallbackRule = AspectRatioStrategy.FALLBACK_RULE_NONE; break; + case UNKNOWN: + default: + // Default to nativeFallbackRule -1. + break; } return new AspectRatioStrategy(nativeAspectRatio, nativeFallbackRule); } @@ -66,9 +75,9 @@ public AspectRatioStrategyFallbackRule getFallbackRule( return AspectRatioStrategyFallbackRule.AUTO; case AspectRatioStrategy.FALLBACK_RULE_NONE: return AspectRatioStrategyFallbackRule.NONE; + default: + return AspectRatioStrategyFallbackRule.UNKNOWN; } - - return AspectRatioStrategyFallbackRule.UNKNOWN; } @NonNull @@ -81,8 +90,8 @@ public AspectRatio getPreferredAspectRatio(@NonNull AspectRatioStrategy pigeonIn return AspectRatio.RATIO4TO3; case androidx.camera.core.AspectRatio.RATIO_DEFAULT: return AspectRatio.RATIO_DEFAULT; + default: + return AspectRatio.UNKNOWN; } - - return AspectRatio.UNKNOWN; } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlProxyApi.java index 4d41de7e4e9..40a4e71c870 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlProxyApi.java @@ -52,10 +52,12 @@ public void addCaptureRequestOptions( Futures.addCallback( addCaptureRequestOptionsFuture, new FutureCallback<>() { + @Override public void onSuccess(Void voidResult) { ResultCompat.success(null, callback); } + @Override public void onFailure(@NonNull Throwable t) { ResultCompat.failure(t, callback); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoProxyApi.java index f9ee09c07b8..87a8f38ef8f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoProxyApi.java @@ -45,7 +45,7 @@ public Object getCameraCharacteristic( return null; } - if (key == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) { + if (CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.equals(key)) { switch ((Integer) result) { case CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3: return InfoSupportedHardwareLevel.LEVEL3; @@ -57,9 +57,11 @@ public Object getCameraCharacteristic( return InfoSupportedHardwareLevel.LEGACY; case CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED: return InfoSupportedHardwareLevel.LIMITED; + default: + // Fall through to return result. + break; } } - return result; } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraCharacteristicsProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraCharacteristicsProxyApi.java index 5cab2f35e69..458f9315d0f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraCharacteristicsProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraCharacteristicsProxyApi.java @@ -25,7 +25,7 @@ public CameraCharacteristics.Key infoSupportedHardwareLevel() { @NonNull @Override - public android.hardware.camera2.CameraCharacteristics.Key sensorOrientation() { + public CameraCharacteristics.Key sensorOrientation() { return CameraCharacteristics.SENSOR_ORIENTATION; } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlProxyApi.java index cee3178df80..42b5d8a3121 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlProxyApi.java @@ -42,10 +42,12 @@ public void enableTorch( Futures.addCallback( enableTorchFuture, new FutureCallback<>() { + @Override public void onSuccess(Void voidResult) { ResultCompat.success(null, callback); } + @Override public void onFailure(@NonNull Throwable t) { ResultCompat.failure(t, callback); } @@ -64,10 +66,12 @@ public void setZoomRatio( Futures.addCallback( setZoomRatioFuture, new FutureCallback<>() { + @Override public void onSuccess(Void voidResult) { ResultCompat.success(null, callback); } + @Override public void onFailure(@NonNull Throwable t) { if (t instanceof CameraControl.OperationCanceledException) { // Operation was canceled due to camera being closed or a new request was submitted, which @@ -93,10 +97,12 @@ public void startFocusAndMetering( Futures.addCallback( focusMeteringResultFuture, new FutureCallback<>() { + @Override public void onSuccess(FocusMeteringResult focusMeteringResult) { ResultCompat.success(focusMeteringResult, callback); } + @Override public void onFailure(@NonNull Throwable t) { if (t instanceof CameraControl.OperationCanceledException) { // Operation was canceled due to camera being closed or a new request was submitted, which @@ -120,10 +126,12 @@ public void cancelFocusAndMetering( Futures.addCallback( cancelFocusAndMeteringFuture, new FutureCallback<>() { + @Override public void onSuccess(Void voidResult) { ResultCompat.success(null, callback); } + @Override public void onFailure(@NonNull Throwable t) { ResultCompat.failure(t, callback); } @@ -142,10 +150,12 @@ public void setExposureCompensationIndex( Futures.addCallback( setExposureCompensationIndexFuture, new FutureCallback<>() { + @Override public void onSuccess(Integer integerResult) { ResultCompat.success(integerResult.longValue(), callback); } + @Override public void onFailure(@NonNull Throwable t) { if (t instanceof CameraControl.OperationCanceledException) { // Operation was canceled due to camera being closed or a new request was submitted, which diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraIntegerRangeProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraIntegerRangeProxyApi.java index 6ed0428990e..1da15481796 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraIntegerRangeProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraIntegerRangeProxyApi.java @@ -24,12 +24,12 @@ public Range pigeon_defaultConstructor(long lower, long upper) { } @Override - public long lower(android.util.Range pigeonInstance) { + public long lower(Range pigeonInstance) { return (Integer) pigeonInstance.getLower(); } @Override - public long upper(android.util.Range pigeonInstance) { + public long upper(Range pigeonInstance) { return (Integer) pigeonInstance.getUpper(); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java index ed80da9ea2c..7afc7035934 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java @@ -13,12 +13,11 @@ import androidx.annotation.VisibleForTesting; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import io.flutter.plugin.common.PluginRegistry; public final class CameraPermissionsManager { interface PermissionsRegistry { - @SuppressWarnings("deprecation") - void addListener( - io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener handler); + void addListener(PluginRegistry.RequestPermissionsResultListener handler); } interface ResultCallback { @@ -84,9 +83,8 @@ private boolean hasAudioPermission(Activity activity) { } @VisibleForTesting - @SuppressWarnings("deprecation") static final class CameraRequestPermissionsListener - implements io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener { + implements PluginRegistry.RequestPermissionsResultListener { // There's no way to unregister permission listeners in the v1 embedding, so we'll be called // duplicate times in cases where the user denies and then grants a permission. Keep track of if diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorProxyApi.java index b1eb4121060..a84e16bcfc2 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorProxyApi.java @@ -47,13 +47,13 @@ public CameraSelector pigeon_defaultConstructor(@Nullable LensFacing requireLens @NonNull @Override - public androidx.camera.core.CameraSelector defaultBackCamera() { + public CameraSelector defaultBackCamera() { return CameraSelector.DEFAULT_BACK_CAMERA; } @NonNull @Override - public androidx.camera.core.CameraSelector defaultFrontCamera() { + public CameraSelector defaultFrontCamera() { return CameraSelector.DEFAULT_FRONT_CAMERA; } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateProxyApi.java index 33f164a0193..fa415358669 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateProxyApi.java @@ -33,9 +33,8 @@ public CameraStateType type(CameraState pigeonInstance) { return CameraStateType.CLOSING; case CLOSED: return CameraStateType.CLOSED; - default: - return CameraStateType.UNKNOWN; } + return CameraStateType.UNKNOWN; } @Nullable diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateStateErrorProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateStateErrorProxyApi.java index 6864a3dd549..f4449c4e979 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateStateErrorProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateStateErrorProxyApi.java @@ -36,7 +36,7 @@ public CameraStateErrorCode code(CameraState.StateError pigeonInstance) { case CameraState.ERROR_STREAM_CONFIG: return CameraStateErrorCode.STREAM_CONFIG; default: - return io.flutter.plugins.camerax.CameraStateErrorCode.UNKNOWN; + return CameraStateErrorCode.UNKNOWN; } } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt index 58f599dee23..1eeb0e98de8 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -3643,7 +3643,7 @@ abstract class PigeonApiVideoRecordEventListener( abstract class PigeonApiPendingRecording( open val pigeonRegistrar: CameraXLibraryPigeonProxyApiRegistrar ) { - /** Enables audio to be recorded for this recording. */ + /** Enables/disables audio to be recorded for this recording. */ abstract fun withAudioEnabled( pigeon_instance: androidx.camera.video.PendingRecording, initialMuted: Boolean diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestProxyApi.java index 9556baccc4b..543be292bb9 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestProxyApi.java @@ -19,7 +19,7 @@ class CaptureRequestProxyApi extends PigeonApiCaptureRequest { @NonNull @Override - public android.hardware.camera2.CaptureRequest.Key controlAELock() { + public CaptureRequest.Key controlAELock() { return CaptureRequest.CONTROL_AE_LOCK; } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java index 382dd2de8fb..f4b509bfe9a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java @@ -4,6 +4,7 @@ package io.flutter.plugins.camerax; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -47,6 +48,8 @@ Context getContext() { *

When orientation information is updated, the callback method of the {@link * DeviceOrientationManagerProxyApi} is called with the new orientation. */ + @SuppressLint( + "UnprotectedReceiver") // orientationIntentFilter only listens to protected broadcast public void start() { stop(); @@ -68,7 +71,7 @@ protected OrientationEventListener createOrientationEventListener() { return new OrientationEventListener(getContext()) { @Override public void onOrientationChanged(int orientation) { - handleUIOrientationChange(); + handleUiOrientationChange(); } }; } @@ -91,8 +94,8 @@ public void stop() { * class. */ @VisibleForTesting - void handleUIOrientationChange() { - PlatformChannel.DeviceOrientation orientation = getUIOrientation(); + void handleUiOrientationChange() { + PlatformChannel.DeviceOrientation orientation = getUiOrientation(); handleOrientationChange(this, orientation, lastOrientation, api); lastOrientation = orientation; } @@ -143,7 +146,7 @@ public void run() { // Configuration.ORIENTATION_SQUARE is deprecated. @SuppressWarnings("deprecation") @NonNull - PlatformChannel.DeviceOrientation getUIOrientation() { + PlatformChannel.DeviceOrientation getUiOrientation() { final int rotation = getDefaultRotation(); final int orientation = getContext().getResources().getConfiguration().orientation; diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerProxyApi.java index ef869884380..91da0727886 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerProxyApi.java @@ -48,6 +48,6 @@ public long getDefaultDisplayRotation(@NonNull DeviceOrientationManager pigeonIn @NonNull @Override public String getUiOrientation(@NonNull DeviceOrientationManager pigeonInstance) { - return pigeonInstance.getUIOrientation().toString(); + return pigeonInstance.getUiOrientation().toString(); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderProxyApi.java index a19a33c01b0..60f8daa6aab 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderProxyApi.java @@ -51,8 +51,7 @@ public void disableAutoCancel(FocusMeteringAction.Builder pigeonInstance) { @NonNull @Override - public androidx.camera.core.FocusMeteringAction build( - FocusMeteringAction.Builder pigeonInstance) { + public FocusMeteringAction build(FocusMeteringAction.Builder pigeonInstance) { return pigeonInstance.build(); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionProxyApi.java index cd38caecef7..e9ee875942c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionProxyApi.java @@ -27,15 +27,13 @@ public List meteringPointsAe(FocusMeteringAction pigeonInstance) @NonNull @Override - public List meteringPointsAf( - FocusMeteringAction pigeonInstance) { + public List meteringPointsAf(FocusMeteringAction pigeonInstance) { return pigeonInstance.getMeteringPointsAf(); } @NonNull @Override - public List meteringPointsAwb( - FocusMeteringAction pigeonInstance) { + public List meteringPointsAwb(FocusMeteringAction pigeonInstance) { return pigeonInstance.getMeteringPointsAwb(); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisProxyApi.java index 8d5efdd05e4..b0eadba8d0f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisProxyApi.java @@ -43,8 +43,7 @@ public ProxyApiRegistrar getPigeonRegistrar() { } @Override - public void setAnalyzer( - ImageAnalysis pigeonInstance, @NonNull androidx.camera.core.ImageAnalysis.Analyzer analyzer) { + public void setAnalyzer(ImageAnalysis pigeonInstance, @NonNull ImageAnalysis.Analyzer analyzer) { getPigeonRegistrar() .getInstanceManager() .setClearFinalizedWeakReferencesInterval( diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java index a0970da5b33..161f91061d2 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java @@ -38,7 +38,7 @@ public ProxyApiRegistrar getPigeonRegistrar() { @NonNull @Override public ImageCapture pigeon_defaultConstructor( - @Nullable androidx.camera.core.resolutionselector.ResolutionSelector resolutionSelector, + @Nullable ResolutionSelector resolutionSelector, @Nullable Long targetRotation, @Nullable CameraXFlashMode flashMode) { final ImageCapture.Builder builder = new ImageCapture.Builder(); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewProxyApi.java index 8fedd79f245..5ebb5fbb739 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewProxyApi.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.camera.core.Preview; +import androidx.camera.core.ResolutionInfo; import androidx.camera.core.SurfaceRequest; import androidx.camera.core.resolutionselector.ResolutionSelector; import io.flutter.view.TextureRegistry; @@ -85,7 +86,7 @@ public boolean surfaceProducerHandlesCropAndRotation(@NonNull Preview pigeonInst @Nullable @Override - public androidx.camera.core.ResolutionInfo getResolutionInfo(Preview pigeonInstance) { + public ResolutionInfo getResolutionInfo(Preview pigeonInstance) { return pigeonInstance.getResolutionInfo(); } @@ -103,6 +104,7 @@ Preview.SurfaceProvider createSurfaceProvider( // get destroyed. surfaceProducer.setCallback( new TextureRegistry.SurfaceProducer.Callback() { + @Override public void onSurfaceAvailable() { // Do nothing. The Preview.SurfaceProvider will handle this whenever a new // Surface is needed. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderProxyApi.java index 86e62ec661a..05bf25209e4 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderProxyApi.java @@ -14,6 +14,7 @@ import androidx.lifecycle.LifecycleOwner; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; +import java.util.concurrent.ExecutionException; import kotlin.Result; import kotlin.Unit; import kotlin.jvm.functions.Function1; @@ -45,7 +46,7 @@ public void getInstance( try { // Camera provider is now guaranteed to be available. ResultCompat.success(processCameraProviderFuture.get(), callback); - } catch (Exception e) { + } catch (InterruptedException | ExecutionException e) { ResultCompat.failure(e, callback); } }, diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyApiRegistrar.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyApiRegistrar.java index 42042488c02..436e850f2ac 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyApiRegistrar.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyApiRegistrar.java @@ -60,7 +60,7 @@ void onFailure(@NonNull String methodName, @NonNull Throwable throwable) { } // PreviewProxyApi maintains a state to track SurfaceProducers provided by the Flutter engine. - @NonNull private final PreviewProxyApi previewProxyApi = new PreviewProxyApi(this); + @Nullable private PreviewProxyApi previewProxyApi; public ProxyApiRegistrar( @NonNull BinaryMessenger binaryMessenger, @@ -140,7 +140,8 @@ long getDefaultClearFinalizedWeakReferencesInterval() { return 3000; } - @SuppressWarnings("deprecation") + @SuppressWarnings( + "deprecation") // getSystemService was the way of getting the default display prior to API 30 @Nullable Display getDisplay() { if (sdkIsAtLeast(Build.VERSION_CODES.R)) { @@ -220,6 +221,9 @@ public DeviceOrientationManagerProxyApi getPigeonApiDeviceOrientationManager() { @NonNull @Override public PigeonApiPreview getPigeonApiPreview() { + if (previewProxyApi == null) { + previewProxyApi = new PreviewProxyApi(this); + } return previewProxyApi; } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorProxyApi.java index 59d946dcf17..11881bf86db 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorProxyApi.java @@ -7,6 +7,7 @@ import android.util.Size; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.camera.core.CameraInfo; import androidx.camera.video.FallbackStrategy; import androidx.camera.video.Quality; import androidx.camera.video.QualitySelector; @@ -53,8 +54,7 @@ public QualitySelector fromOrderedList( @Nullable @Override - public Size getResolution( - @NonNull androidx.camera.core.CameraInfo cameraInfo, @NonNull VideoQuality quality) { + public Size getResolution(@NonNull CameraInfo cameraInfo, @NonNull VideoQuality quality) { return QualitySelector.getResolution(cameraInfo, getNativeQuality(quality)); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java index 0f326281b9a..dc11e4a2b7f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java @@ -33,7 +33,7 @@ public ProxyApiRegistrar getPigeonRegistrar() { public Recorder pigeon_defaultConstructor( @Nullable Long aspectRatio, @Nullable Long targetVideoEncodingBitRate, - @Nullable androidx.camera.video.QualitySelector qualitySelector) { + @Nullable QualitySelector qualitySelector) { final Recorder.Builder builder = new Recorder.Builder(); if (aspectRatio != null) { builder.setAspectRatio(aspectRatio.intValue()); @@ -71,7 +71,7 @@ public PendingRecording prepareRecording(Recorder pigeonInstance, @NonNull Strin } @NonNull - File openTempFile(@NonNull String path) throws RuntimeException { + File openTempFile(@NonNull String path) { try { return new File(path); } catch (NullPointerException | SecurityException e) { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionInfoProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionInfoProxyApi.java index 2be20194da1..230854ebab4 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionInfoProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionInfoProxyApi.java @@ -4,6 +4,7 @@ package io.flutter.plugins.camerax; +import android.util.Size; import androidx.annotation.NonNull; import androidx.camera.core.ResolutionInfo; @@ -20,7 +21,7 @@ class ResolutionInfoProxyApi extends PigeonApiResolutionInfo { @NonNull @Override - public android.util.Size resolution(ResolutionInfo pigeonInstance) { + public Size resolution(ResolutionInfo pigeonInstance) { return pigeonInstance.getResolution(); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyProxyApi.java index 531753b4eda..d0bd58c40c9 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyProxyApi.java @@ -22,7 +22,7 @@ class ResolutionStrategyProxyApi extends PigeonApiResolutionStrategy { @NonNull @Override public ResolutionStrategy pigeon_defaultConstructor( - @NonNull android.util.Size boundSize, @NonNull ResolutionStrategyFallbackRule fallbackRule) { + @NonNull Size boundSize, @NonNull ResolutionStrategyFallbackRule fallbackRule) { int nativeFallbackRule = -1; switch (fallbackRule) { case CLOSEST_HIGHER: @@ -40,6 +40,9 @@ public ResolutionStrategy pigeon_defaultConstructor( case NONE: nativeFallbackRule = ResolutionStrategy.FALLBACK_RULE_NONE; break; + case UNKNOWN: + // Default to nativeFallbackRule -1. + break; } return new ResolutionStrategy(boundSize, nativeFallbackRule); } @@ -71,8 +74,8 @@ public ResolutionStrategyFallbackRule getFallbackRule( return ResolutionStrategyFallbackRule.CLOSEST_LOWER_THEN_HIGHER; case ResolutionStrategy.FALLBACK_RULE_NONE: return ResolutionStrategyFallbackRule.NONE; + default: + return ResolutionStrategyFallbackRule.UNKNOWN; } - - return ResolutionStrategyFallbackRule.UNKNOWN; } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManagerProxyApi.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManagerProxyApi.java index 53a1626414e..65aefe0f53d 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManagerProxyApi.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManagerProxyApi.java @@ -104,7 +104,7 @@ public String getTempFilePath( throw new RuntimeException( "getTempFilePath_failure", new Throwable( - "SystemServicesHostApiImpl.getTempFilePath encountered an exception: " + e)); + "SystemServicesHostApiImpl.getTempFilePath encountered an exception: " + e, e)); } } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java index 8056de6496c..f4a60b19be5 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java @@ -6,16 +6,23 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; import androidx.camera.camera2.interop.Camera2CameraInfo; import androidx.camera.core.CameraInfo; +import java.util.Map; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +@RunWith(RobolectricTestRunner.class) public class Camera2CameraInfoTest { @Test public void from_createsInstanceFromCameraInfoInstance() { @@ -47,17 +54,54 @@ public void getCameraId_returnsExpectedId() { assertEquals(value, api.getCameraId(instance)); } + @Config(minSdk = 28) @SuppressWarnings("unchecked") @Test - public void getCameraCharacteristic_returnsCorrespondingValueOfKey() { + public void getCameraCharacteristic_returnsCorrespondingValueOfKeyWhenKeyNotRecognized() { final PigeonApiCamera2CameraInfo api = new TestProxyApiRegistrar().getPigeonApiCamera2CameraInfo(); final Camera2CameraInfo instance = mock(Camera2CameraInfo.class); - final CameraCharacteristics.Key key = mock(CameraCharacteristics.Key.class); - final int value = -1; + final CameraCharacteristics.Key key = CameraCharacteristics.INFO_VERSION; + final String value = "version info"; when(instance.getCameraCharacteristic(key)).thenReturn(value); assertEquals(value, api.getCameraCharacteristic(instance, key)); } + + @Config(minSdk = 21) + @SuppressWarnings("unchecked") + @Test + public void getCameraCharacteristic_returnsExpectedCameraHardwareLevelWhenRequested() { + final PigeonApiCamera2CameraInfo api = + new TestProxyApiRegistrar().getPigeonApiCamera2CameraInfo(); + + final Camera2CameraInfo instance = mock(Camera2CameraInfo.class); + final CameraCharacteristics.Key key = + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL; + + // Test known values. + Map cameraHardwareLevelsToPigeonConstants = + Map.of( + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3, InfoSupportedHardwareLevel.LEVEL3, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, + InfoSupportedHardwareLevel.EXTERNAL, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, InfoSupportedHardwareLevel.FULL, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, InfoSupportedHardwareLevel.LEGACY, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + InfoSupportedHardwareLevel.LIMITED); + + for (int cameraHardwareLevel : cameraHardwareLevelsToPigeonConstants.keySet()) { + when(instance.getCameraCharacteristic(key)).thenReturn(cameraHardwareLevel); + assertEquals( + cameraHardwareLevelsToPigeonConstants.get(cameraHardwareLevel), + api.getCameraCharacteristic(instance, key)); + reset(instance); + } + + // Test unknown value. + int testUnknownValue = -1; + when(instance.getCameraCharacteristic(key)).thenReturn(testUnknownValue); + assertEquals(testUnknownValue, api.getCameraCharacteristic(instance, key)); + } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerApiTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerApiTest.java index f3f7d0d579b..306f2332ad0 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerApiTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerApiTest.java @@ -58,7 +58,7 @@ public void getUiOrientation_returnsExpectedOrientation() { final DeviceOrientationManager instance = mock(DeviceOrientationManager.class); final PlatformChannel.DeviceOrientation orientation = PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; - when(instance.getUIOrientation()).thenReturn(orientation); + when(instance.getUiOrientation()).thenReturn(orientation); assertEquals(orientation.toString(), api.getUiOrientation(instance)); } @@ -74,11 +74,11 @@ public void onDeviceOrientationChanged_shouldSendMessageWhenOrientationIsUpdated new DeviceOrientationManager(mockApi) { @NonNull @Override - PlatformChannel.DeviceOrientation getUIOrientation() { + PlatformChannel.DeviceOrientation getUiOrientation() { return orientation; } }; - instance.handleUIOrientationChange(); + instance.handleUiOrientationChange(); verify(mockApi).onDeviceOrientationChanged(eq(instance), eq(orientation.toString()), any()); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java index 56456cf4ecc..de5faafe801 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java @@ -67,13 +67,13 @@ Display getDisplay() { public void start_createsExpectedOrientationEventListener() { DeviceOrientationManager deviceOrientationManagerSpy = spy(deviceOrientationManager); - doNothing().when(deviceOrientationManagerSpy).handleUIOrientationChange(); + doNothing().when(deviceOrientationManagerSpy).handleUiOrientationChange(); deviceOrientationManagerSpy.start(); deviceOrientationManagerSpy.orientationEventListener.onOrientationChanged( /* some device orientation */ 3); - verify(deviceOrientationManagerSpy).handleUIOrientationChange(); + verify(deviceOrientationManagerSpy).handleUiOrientationChange(); } @Test @@ -125,50 +125,50 @@ public void handleOrientationChange_shouldNotSendMessageWhenOrientationIsNotUpda } @Test - public void getUIOrientation() { + public void getUiOrientation() { // Orientation portrait and rotation of 0 should translate to "PORTRAIT_UP". setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); - DeviceOrientation uiOrientation = deviceOrientationManager.getUIOrientation(); + DeviceOrientation uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); // Orientation portrait and rotation of 90 should translate to "PORTRAIT_UP". setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); // Orientation portrait and rotation of 180 should translate to "PORTRAIT_DOWN". setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); // Orientation portrait and rotation of 270 should translate to "PORTRAIT_DOWN". setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); // Orientation landscape and rotation of 0 should translate to "LANDSCAPE_LEFT". setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); // Orientation landscape and rotation of 90 should translate to "LANDSCAPE_LEFT". setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); // Orientation landscape and rotation of 180 should translate to "LANDSCAPE_RIGHT". setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); // Orientation landscape and rotation of 270 should translate to "LANDSCAPE_RIGHT". setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); // Orientation undefined should default to "PORTRAIT_UP". setUpUIOrientationMocks(Configuration.ORIENTATION_UNDEFINED, Surface.ROTATION_0); - uiOrientation = deviceOrientationManager.getUIOrientation(); + uiOrientation = deviceOrientationManager.getUiOrientation(); assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); } diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index ba337d81f32..5d266c09ce7 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -307,8 +307,18 @@ class PigeonInstanceManager { /// /// Returns the randomly generated id of the [instance] added. int addDartCreatedInstance(PigeonInternalProxyApiBaseClass instance) { + assert(getIdentifier(instance) == null); + final int identifier = _nextUniqueIdentifier(); - _addInstanceWithIdentifier(instance, identifier); + _identifiers[instance] = identifier; + _weakInstances[identifier] = WeakReference( + instance, + ); + _finalizer.attach(instance, identifier, detach: instance); + + final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; return identifier; } @@ -340,9 +350,15 @@ class PigeonInstanceManager { /// it was removed. Returns `null` if [identifier] was not associated with /// any strong reference. /// - /// This does not remove the weak referenced instance associated with - /// [identifier]. This can be done with [removeWeakReference]. + /// Throws an `AssertionError` if the weak referenced instance associated with + /// [identifier] is not removed first. This can be done with + /// [removeWeakReference]. T? remove(int identifier) { + final T? instance = _weakInstances[identifier]?.target as T?; + assert( + instance == null, + 'A strong instance with identifier $identifier is being removed despite the weak reference still existing: $instance', + ); return _strongInstances.remove(identifier) as T?; } @@ -394,32 +410,16 @@ class PigeonInstanceManager { /// /// Throws assertion error if the instance or its identifier has already been /// added. - /// - /// Returns unique identifier of the [instance] added. void addHostCreatedInstance( PigeonInternalProxyApiBaseClass instance, int identifier, - ) { - _addInstanceWithIdentifier(instance, identifier); - } - - void _addInstanceWithIdentifier( - PigeonInternalProxyApiBaseClass instance, - int identifier, ) { assert(!containsIdentifier(identifier)); assert(getIdentifier(instance) == null); assert(identifier >= 0); _identifiers[instance] = identifier; - _weakInstances[identifier] = WeakReference( - instance, - ); - _finalizer.attach(instance, identifier, detach: instance); - - final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy(); - _identifiers[copy] = identifier; - _strongInstances[identifier] = copy; + _strongInstances[identifier] = instance; } /// Whether this manager contains the given [identifier]. @@ -4359,7 +4359,7 @@ class PendingRecording extends PigeonInternalProxyApiBaseClass { } } - /// Enables audio to be recorded for this recording. + /// Enables/disables audio to be recorded for this recording. Future withAudioEnabled(bool initialMuted) async { final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = _pigeonVar_codecPendingRecording; diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 1299af343e3..dc0f0ad90a8 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.18 +version: 0.6.18+2 environment: sdk: ^3.7.0 @@ -31,7 +31,7 @@ dev_dependencies: sdk: flutter leak_tracker_flutter_testing: any mockito: ^5.4.4 - pigeon: ^25.3.1 + pigeon: ^25.3.2 topics: - camera diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index fa4e97db269..7e16bdfffff 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.9.19+2 + +* Adds the `Camera` Swift protocol. +* Adds `DefaultCamera`, a `FLTCam`-based implementation of the `Camera` protocol. +* Migrates sample buffer delegates and `FlutterTexture` protocol implementations to `DefaultCamera`. + ## 0.9.19+1 * Adds `audioCaptureDeviceFactory` to `FLTCamConfiguration`. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift index 22d6b9485ae..e21497d1a1e 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.swift @@ -4,11 +4,11 @@ import XCTest +@testable import camera_avfoundation + // Import Objectice-C part of the implementation when SwiftPM is used. #if canImport(camera_avfoundation_objc) import camera_avfoundation_objc -#else - import camera_avfoundation #endif /// Utils for creating default class instances used in tests @@ -81,16 +81,16 @@ enum CameraTestUtils { return configuration } - static func createTestCamera(_ configuration: FLTCamConfiguration) -> FLTCam { - return FLTCam(configuration: configuration, error: nil) + static func createTestCamera(_ configuration: FLTCamConfiguration) -> DefaultCamera { + return DefaultCamera(configuration: configuration, error: nil) } - static func createTestCamera() -> FLTCam { + static func createTestCamera() -> DefaultCamera { return createTestCamera(createTestCameraConfiguration()) } static func createCameraWithCaptureSessionQueue(_ captureSessionQueue: DispatchQueue) - -> FLTCam + -> DefaultCamera { let configuration = createTestCameraConfiguration() configuration.captureSessionQueue = captureSessionQueue diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamExposureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamExposureTests.swift index f60d4a43f2c..20952741f71 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamExposureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamExposureTests.swift @@ -12,7 +12,7 @@ import XCTest #endif final class FLTCamExposureTests: XCTestCase { - private func createCamera() -> (FLTCam, MockCaptureDevice, MockDeviceOrientationProvider) { + private func createCamera() -> (Camera, MockCaptureDevice, MockDeviceOrientationProvider) { let mockDevice = MockCaptureDevice() let mockDeviceOrientationProvider = MockDeviceOrientationProvider() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamFocusTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamFocusTests.swift index db82db82446..580ebdab109 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamFocusTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamFocusTests.swift @@ -13,7 +13,7 @@ import XCTest #endif final class FLTCamSetFocusModeTests: XCTestCase { - private func createCamera() -> (FLTCam, MockCaptureDevice, MockDeviceOrientationProvider) { + private func createCamera() -> (Camera, MockCaptureDevice, MockDeviceOrientationProvider) { let mockDevice = MockCaptureDevice() let mockDeviceOrientationProvider = MockDeviceOrientationProvider() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift index 762152db317..cd8d5d858a8 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift @@ -13,7 +13,7 @@ import XCTest #endif final class FLTCamSetDeviceOrientationTests: XCTestCase { - private func createCamera() -> (FLTCam, MockCaptureConnection, MockCaptureConnection) { + private func createCamera() -> (Camera, MockCaptureConnection, MockCaptureConnection) { let camera = CameraTestUtils.createTestCamera() let mockCapturePhotoOutput = MockCapturePhotoOutput() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetFlashModeTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetFlashModeTests.swift index a6e51dd9b7e..29ade54f7fe 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetFlashModeTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetFlashModeTests.swift @@ -13,7 +13,7 @@ import XCTest #endif final class FLTCamSetFlashModeTests: XCTestCase { - private func createCamera() -> (FLTCam, MockCaptureDevice, MockCapturePhotoOutput) { + private func createCamera() -> (Camera, MockCaptureDevice, MockCapturePhotoOutput) { let mockDevice = MockCaptureDevice() let mockCapturePhotoOutput = MockCapturePhotoOutput() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamZoomTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamZoomTests.swift index 8cc507d9dbb..618d87edde3 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamZoomTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamZoomTests.swift @@ -13,7 +13,7 @@ import XCTest #endif final class FLTCamZoomTests: XCTestCase { - private func createCamera() -> (FLTCam, MockCaptureDevice) { + private func createCamera() -> (Camera, MockCaptureDevice) { let mockDevice = MockCaptureDevice() let configuration = CameraTestUtils.createTestCameraConfiguration() diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift index 14e30643e3a..3f719dacdb2 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import camera_avfoundation +@testable import camera_avfoundation // Import Objectice-C part of the implementation when SwiftPM is used. #if canImport(camera_avfoundation_objc) import camera_avfoundation_objc #endif -final class MockCamera: FLTCam { +final class MockCamera: NSObject, Camera { var setDartApiStub: ((FCPCameraEventApi?) -> Void)? var setOnFrameAvailableStub: (((() -> Void)?) -> Void)? var getMinimumExposureOffsetStub: (() -> CGFloat)? @@ -42,7 +42,7 @@ final class MockCamera: FLTCam { var startImageStreamStub: ((FlutterBinaryMessenger) -> Void)? var stopImageStreamStub: (() -> Void)? - override var dartAPI: FCPCameraEventApi { + var dartAPI: FCPCameraEventApi? { get { preconditionFailure("Attempted to access unimplemented property: dartAPI") } @@ -51,7 +51,7 @@ final class MockCamera: FLTCam { } } - override var onFrameAvailable: (() -> Void) { + var onFrameAvailable: (() -> Void)? { get { preconditionFailure("Attempted to access unimplemented property: onFrameAvailable") } @@ -60,147 +60,149 @@ final class MockCamera: FLTCam { } } - override var minimumExposureOffset: CGFloat { + var videoFormat: FourCharCode = kCVPixelFormatType_32BGRA + + var isPreviewPaused: Bool = false + + var minimumExposureOffset: CGFloat { return getMinimumExposureOffsetStub?() ?? 0 } - override var maximumExposureOffset: CGFloat { + var maximumExposureOffset: CGFloat { return getMaximumExposureOffsetStub?() ?? 0 } - override var minimumAvailableZoomFactor: CGFloat { + var minimumAvailableZoomFactor: CGFloat { return getMinimumAvailableZoomFactorStub?() ?? 0 } - override var maximumAvailableZoomFactor: CGFloat { + var maximumAvailableZoomFactor: CGFloat { return getMaximumAvailableZoomFactorStub?() ?? 0 } - override func setUpCaptureSessionForAudioIfNeeded() { + func setUpCaptureSessionForAudioIfNeeded() { setUpCaptureSessionForAudioIfNeededStub?() } - override func reportInitializationState() {} + func reportInitializationState() {} - override func receivedImageStreamData() { + func receivedImageStreamData() { receivedImageStreamDataStub?() } - override func start() { + func start() { startStub?() } - override func stop() {} + func stop() {} - override func startVideoRecording( + func startVideoRecording( completion: @escaping (FlutterError?) -> Void, messengerForStreaming messenger: FlutterBinaryMessenger? ) { startVideoRecordingStub?(completion, messenger) } - override func pauseVideoRecording() { + func pauseVideoRecording() { pauseVideoRecordingStub?() } - override func resumeVideoRecording() { + func resumeVideoRecording() { resumeVideoRecordingStub?() } - override func stopVideoRecording(completion: @escaping (String?, FlutterError?) -> Void) { + func stopVideoRecording(completion: @escaping (String?, FlutterError?) -> Void) { stopVideoRecordingStub?(completion) } - override func captureToFile(completion: @escaping (String?, FlutterError?) -> Void) { + func captureToFile(completion: @escaping (String?, FlutterError?) -> Void) { captureToFileStub?(completion) } - override func setDeviceOrientation(_ orientation: UIDeviceOrientation) { + func setDeviceOrientation(_ orientation: UIDeviceOrientation) { setDeviceOrientationStub?(orientation) } - override func lockCaptureOrientation(_ orientation: FCPPlatformDeviceOrientation) { + func lockCaptureOrientation(_ orientation: FCPPlatformDeviceOrientation) { lockCaptureOrientationStub?(orientation) } - override func unlockCaptureOrientation() { + func unlockCaptureOrientation() { unlockCaptureOrientationStub?() } - override func setImageFileFormat(_ fileFormat: FCPPlatformImageFileFormat) { + func setImageFileFormat(_ fileFormat: FCPPlatformImageFileFormat) { setImageFileFormatStub?(fileFormat) } - override func setExposureMode(_ mode: FCPPlatformExposureMode) { + func setExposureMode(_ mode: FCPPlatformExposureMode) { setExposureModeStub?(mode) } - override func setExposureOffset(_ offset: Double) { + func setExposureOffset(_ offset: Double) { setExposureOffsetStub?(offset) } - override func setExposurePoint( + func setExposurePoint( _ point: FCPPlatformPoint?, withCompletion: @escaping (FlutterError?) -> Void ) { setExposurePointStub?(point, withCompletion) } - override func setFocusMode(_ mode: FCPPlatformFocusMode) { + func setFocusMode(_ mode: FCPPlatformFocusMode) { setFocusModeStub?(mode) } - override func setFocusPoint( - _ point: FCPPlatformPoint?, completion: @escaping (FlutterError?) -> Void - ) { + func setFocusPoint(_ point: FCPPlatformPoint?, completion: @escaping (FlutterError?) -> Void) { setFocusPointStub?(point, completion) } - override func setZoomLevel( + func setZoomLevel( _ zoom: CGFloat, withCompletion completion: @escaping (FlutterError?) -> Void ) { setZoomLevelStub?(zoom, completion) } - override func setFlashMode( + func setFlashMode( _ mode: FCPPlatformFlashMode, withCompletion completion: @escaping (FlutterError?) -> Void ) { setFlashModeStub?(mode, completion) } - override func pausePreview() { + func pausePreview() { pausePreviewStub?() } - override func resumePreview() { + func resumePreview() { resumePreviewStub?() } - override func setDescriptionWhileRecording( + func setDescriptionWhileRecording( _ cameraName: String, withCompletion completion: @escaping (FlutterError?) -> Void ) { setDescriptionWhileRecordingStub?(cameraName, completion) } - override func startImageStream(with messenger: FlutterBinaryMessenger) { + func startImageStream(with messenger: FlutterBinaryMessenger) { startImageStreamStub?(messenger) } - override func stopImageStream() { + func stopImageStream() { stopImageStreamStub?() } - override func captureOutput( + func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection ) {} - override func close() {} + func close() {} - override func copyPixelBuffer() -> Unmanaged? { + func copyPixelBuffer() -> Unmanaged? { return nil } } diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift index 93915529f74..ad4b99c86b7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift @@ -14,7 +14,7 @@ import XCTest /// Includes test cases related to photo capture operations for FLTCam class. final class PhotoCaptureTests: XCTestCase { - private func createCam(with captureSessionQueue: DispatchQueue) -> FLTCam { + private func createCam(with captureSessionQueue: DispatchQueue) -> DefaultCamera { let configuration = CameraTestUtils.createTestCameraConfiguration() configuration.captureSessionQueue = captureSessionQueue return CameraTestUtils.createTestCamera(configuration) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index bea41564eb0..eeef97b292a 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -69,7 +69,7 @@ private class FakeMediaSettingsAVWrapper: FLTCamMediaSettingsAVWrapper { /// Includes test cases related to sample buffer handling for FLTCam class. final class CameraSampleBufferTests: XCTestCase { private func createCamera() -> ( - FLTCam, + DefaultCamera, MockAssetWriter, MockAssetWriterInputPixelBufferAdaptor, MockAssetWriterInput diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift index 129638c5356..edb7a5fc479 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift @@ -33,7 +33,7 @@ private class MockImageStreamHandler: FLTImageStreamHandler { final class StreamingTests: XCTestCase { private func createCamera() -> ( - FLTCam, + DefaultCamera, AVCaptureOutput, CMSampleBuffer, AVCaptureConnection diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift new file mode 100644 index 00000000000..7fdde60680d --- /dev/null +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift @@ -0,0 +1,101 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import AVFoundation +import CoreMotion +import Flutter + +// Import Objectice-C part of the implementation when SwiftPM is used. +#if canImport(camera_avfoundation_objc) + import camera_avfoundation_objc +#endif + +/// A class that manages camera's state and performs camera operations. +protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, + AVCaptureAudioDataOutputSampleBufferDelegate +{ + /// The API instance used to communicate with the Dart side of the plugin. + /// Once initially set, this should only ever be accessed on the main thread. + var dartAPI: FCPCameraEventApi? { get set } + + var onFrameAvailable: (() -> Void)? { get set } + + /// Format used for video and image streaming. + var videoFormat: FourCharCode { get set } + + var isPreviewPaused: Bool { get } + + var minimumAvailableZoomFactor: CGFloat { get } + var maximumAvailableZoomFactor: CGFloat { get } + var minimumExposureOffset: CGFloat { get } + var maximumExposureOffset: CGFloat { get } + + func setUpCaptureSessionForAudioIfNeeded() + + func reportInitializationState() + + /// Acknowledges the receipt of one image stream frame. + func receivedImageStreamData() + + func start() + func stop() + + /// Starts recording a video with an optional streaming messenger. + func startVideoRecording( + completion: @escaping (_ error: FlutterError?) -> Void, + messengerForStreaming: FlutterBinaryMessenger? + ) + func pauseVideoRecording() + func resumeVideoRecording() + func stopVideoRecording(completion: @escaping (_ path: String?, _ error: FlutterError?) -> Void) + + func captureToFile(completion: @escaping (_ path: String?, _ error: FlutterError?) -> Void) + + func setDeviceOrientation(_ orientation: UIDeviceOrientation) + func lockCaptureOrientation(_ orientation: FCPPlatformDeviceOrientation) + func unlockCaptureOrientation() + + func setImageFileFormat(_ fileFormat: FCPPlatformImageFileFormat) + + func setExposureMode(_ mode: FCPPlatformExposureMode) + func setExposureOffset(_ offset: Double) + func setExposurePoint( + _ point: FCPPlatformPoint?, + withCompletion: @escaping (_ error: FlutterError?) -> Void + ) + + func setFocusMode(_ mode: FCPPlatformFocusMode) + func setFocusPoint( + _ point: FCPPlatformPoint?, + completion: @escaping (_ error: FlutterError?) -> Void + ) + + func setZoomLevel(_ zoom: CGFloat, withCompletion: @escaping (_ error: FlutterError?) -> Void) + + func setFlashMode( + _ mode: FCPPlatformFlashMode, + withCompletion: @escaping (_ error: FlutterError?) -> Void + ) + + func pausePreview() + func resumePreview() + + func setDescriptionWhileRecording( + _ cameraName: String, + withCompletion: @escaping (_ error: FlutterError?) -> Void + ) + + func startImageStream(with: FlutterBinaryMessenger) + func stopImageStream() + + // Override to make `AVCaptureVideoDataOutputSampleBufferDelegate`/ + // `AVCaptureAudioDataOutputSampleBufferDelegate` method non optional + override func captureOutput( + _ output: AVCaptureOutput, + didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection + ) + + func close() +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift index 570cd22095e..62d570a578f 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift @@ -24,7 +24,7 @@ public final class CameraPlugin: NSObject, FlutterPlugin { private let captureSessionQueue: DispatchQueue /// An internal camera object that manages camera's state and performs camera operations. - var camera: FLTCam? + var camera: Camera? public static func register(with registrar: FlutterPluginRegistrar) { let instance = CameraPlugin( @@ -248,7 +248,7 @@ extension CameraPlugin: FCPCameraApi { ) var error: NSError? - let newCamera = FLTCam(configuration: camConfiguration, error: &error) + let newCamera = DefaultCamera(configuration: camConfiguration, error: &error) if let error = error { completion(nil, CameraPlugin.flutterErrorFromNSError(error)) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift new file mode 100644 index 00000000000..5fbb65cfc6a --- /dev/null +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -0,0 +1,265 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import CoreMotion + +// Import Objectice-C part of the implementation when SwiftPM is used. +#if canImport(camera_avfoundation_objc) + import camera_avfoundation_objc +#endif + +final class DefaultCamera: FLTCam, Camera { + /// The queue on which `latestPixelBuffer` property is accessed. + /// To avoid unnecessary contention, do not access `latestPixelBuffer` on the `captureSessionQueue`. + private let pixelBufferSynchronizationQueue = DispatchQueue( + label: "io.flutter.camera.pixelBufferSynchronizationQueue") + + /// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback. + /// Used to deliver the latest pixel buffer to the flutter engine via the `copyPixelBuffer` API. + private var latestPixelBuffer: CVPixelBuffer? + private var lastVideoSampleTime = CMTime.zero + private var lastAudioSampleTime = CMTime.zero + + /// Maximum number of frames pending processing. + /// To limit memory consumption, limit the number of frames pending processing. + /// After some testing, 4 was determined to be the best maximuńm value. + /// https://github.com/flutter/plugins/pull/4520#discussion_r766335637 + private var maxStreamingPendingFramesCount = 4 + + func captureOutput( + _ output: AVCaptureOutput, + didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection + ) { + if output == captureVideoOutput.avOutput { + if let newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { + + pixelBufferSynchronizationQueue.sync { + latestPixelBuffer = newBuffer + } + + onFrameAvailable?() + } + } + + guard CMSampleBufferDataIsReady(sampleBuffer) else { + reportErrorMessage("sample buffer is not ready. Skipping sample") + return + } + + if isStreamingImages { + if let eventSink = imageStreamHandler?.eventSink, + streamingPendingFramesCount < maxStreamingPendingFramesCount + { + streamingPendingFramesCount += 1 + + let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! + // Must lock base address before accessing the pixel data + CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) + + let imageWidth = CVPixelBufferGetWidth(pixelBuffer) + let imageHeight = CVPixelBufferGetHeight(pixelBuffer) + + var planes: [[String: Any]] = [] + + let isPlanar = CVPixelBufferIsPlanar(pixelBuffer) + let planeCount = isPlanar ? CVPixelBufferGetPlaneCount(pixelBuffer) : 1 + + for i in 0.. 0 { + currentSampleTime = CMTimeAdd(currentSampleTime, dur) + } + + if audioIsDisconnected { + audioIsDisconnected = false + + audioTimeOffset = + audioTimeOffset.value == 0 + ? CMTimeSubtract(currentSampleTime, lastAudioSampleTime) + : CMTimeAdd(audioTimeOffset, CMTimeSubtract(currentSampleTime, lastAudioSampleTime)) + + return + } + + lastAudioSampleTime = currentSampleTime + + if audioTimeOffset.value != 0 { + if let adjustedSampleBuffer = copySampleBufferWithAdjustedTime( + sampleBuffer, + by: audioTimeOffset) + { + newAudioSample(adjustedSampleBuffer) + } + } else { + newAudioSample(sampleBuffer) + } + } + } + } + + private func copySampleBufferWithAdjustedTime(_ sample: CMSampleBuffer, by offset: CMTime) + -> CMSampleBuffer? + { + var count: CMItemCount = 0 + CMSampleBufferGetSampleTimingInfoArray( + sample, entryCount: 0, arrayToFill: nil, entriesNeededOut: &count) + + let timingInfo = UnsafeMutablePointer.allocate(capacity: Int(count)) + defer { timingInfo.deallocate() } + + CMSampleBufferGetSampleTimingInfoArray( + sample, entryCount: count, arrayToFill: timingInfo, entriesNeededOut: &count) + + for i in 0.. Unmanaged? { + var pixelBuffer: CVPixelBuffer? + pixelBufferSynchronizationQueue.sync { + pixelBuffer = latestPixelBuffer + latestPixelBuffer = nil + } + + if let buffer = pixelBuffer { + return Unmanaged.passRetained(buffer) + } else { + return nil + } + } + + private func reportErrorMessage(_ errorMessage: String) { + FLTEnsureToRunOnMainQueue { [weak self] in + self?.dartAPI?.reportError(errorMessage) { _ in + // Ignore any errors, as this is just an event broadcast. + } + } + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m index 1aa6a4598ea..e67c586eda1 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m @@ -32,48 +32,22 @@ @interface FLTCam () *videoCaptureSession; @property(readonly, nonatomic) NSObject *audioCaptureSession; @property(readonly, nonatomic) NSObject *captureVideoInput; -/// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback. -/// Used to deliver the latest pixel buffer to the flutter engine via the `copyPixelBuffer` API. -@property(readwrite, nonatomic) CVPixelBufferRef latestPixelBuffer; @property(readonly, nonatomic) CGSize captureSize; -@property(strong, nonatomic) NSObject *videoWriter; -@property(strong, nonatomic) NSObject *videoWriterInput; -@property(strong, nonatomic) NSObject *audioWriterInput; @property(strong, nonatomic) NSObject *assetWriterPixelBufferAdaptor; @property(strong, nonatomic) AVCaptureVideoDataOutput *videoOutput; @property(strong, nonatomic) AVCaptureAudioDataOutput *audioOutput; @property(strong, nonatomic) NSString *videoRecordingPath; -@property(assign, nonatomic) BOOL isFirstVideoSample; -@property(assign, nonatomic) BOOL isRecording; -@property(assign, nonatomic) BOOL isRecordingPaused; -@property(assign, nonatomic) BOOL videoIsDisconnected; -@property(assign, nonatomic) BOOL audioIsDisconnected; @property(assign, nonatomic) BOOL isAudioSetup; -/// Number of frames currently pending processing. -@property(assign, nonatomic) int streamingPendingFramesCount; - -/// Maximum number of frames pending processing. -@property(assign, nonatomic) int maxStreamingPendingFramesCount; - @property(assign, nonatomic) UIDeviceOrientation lockedCaptureOrientation; -@property(assign, nonatomic) CMTime lastVideoSampleTime; -@property(assign, nonatomic) CMTime lastAudioSampleTime; -@property(assign, nonatomic) CMTime videoTimeOffset; -@property(assign, nonatomic) CMTime audioTimeOffset; @property(nonatomic) CMMotionManager *motionManager; -@property NSObject *videoAdaptor; /// All FLTCam's state access and capture session related operations should be on run on this queue. @property(strong, nonatomic) dispatch_queue_t captureSessionQueue; -/// The queue on which `latestPixelBuffer` property is accessed. -/// To avoid unnecessary contention, do not access `latestPixelBuffer` on the `captureSessionQueue`. -@property(strong, nonatomic) dispatch_queue_t pixelBufferSynchronizationQueue; /// The queue on which captured photos (not videos) are written to disk. /// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation. @property(strong, nonatomic) dispatch_queue_t photoIOQueue; @@ -109,8 +83,6 @@ - (instancetype)initWithConfiguration:(nonnull FLTCamConfiguration *)configurati _mediaSettingsAVWrapper = configuration.mediaSettingsWrapper; _captureSessionQueue = configuration.captureSessionQueue; - _pixelBufferSynchronizationQueue = - dispatch_queue_create("io.flutter.camera.pixelBufferSynchronizationQueue", NULL); _photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL); _videoCaptureSession = configuration.videoCaptureSession; _audioCaptureSession = configuration.audioCaptureSession; @@ -132,11 +104,6 @@ - (instancetype)initWithConfiguration:(nonnull FLTCamConfiguration *)configurati _assetWriterFactory = configuration.assetWriterFactory; _inputPixelBufferAdaptorFactory = configuration.inputPixelBufferAdaptorFactory; - // To limit memory consumption, limit the number of frames pending processing. - // After some testing, 4 was determined to be the best maximum value. - // https://github.com/flutter/plugins/pull/4520#discussion_r766335637 - _maxStreamingPendingFramesCount = 4; - NSError *localError = nil; AVCaptureConnection *connection = [self createConnection:&localError]; if (localError) { @@ -511,227 +478,6 @@ - (BOOL)setCaptureSessionPreset:(FCPPlatformResolutionPreset)resolutionPreset return bestFormat; } -- (void)captureOutput:(AVCaptureOutput *)output - didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer - fromConnection:(AVCaptureConnection *)connection { - if (output == _captureVideoOutput.avOutput) { - CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); - CFRetain(newBuffer); - - __block CVPixelBufferRef previousPixelBuffer = nil; - // Use `dispatch_sync` to avoid unnecessary context switch under common non-contest scenarios; - // Under rare contest scenarios, it will not block for too long since the critical section is - // quite lightweight. - dispatch_sync(self.pixelBufferSynchronizationQueue, ^{ - // No need weak self because it's dispatch_sync. - previousPixelBuffer = self.latestPixelBuffer; - self.latestPixelBuffer = newBuffer; - }); - if (previousPixelBuffer) { - CFRelease(previousPixelBuffer); - } - if (_onFrameAvailable) { - _onFrameAvailable(); - } - } - if (!CMSampleBufferDataIsReady(sampleBuffer)) { - [self reportErrorMessage:@"sample buffer is not ready. Skipping sample"]; - return; - } - if (_isStreamingImages) { - FlutterEventSink eventSink = _imageStreamHandler.eventSink; - if (eventSink && (self.streamingPendingFramesCount < self.maxStreamingPendingFramesCount)) { - self.streamingPendingFramesCount++; - CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); - // Must lock base address before accessing the pixel data - CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); - - size_t imageWidth = CVPixelBufferGetWidth(pixelBuffer); - size_t imageHeight = CVPixelBufferGetHeight(pixelBuffer); - - NSMutableArray *planes = [NSMutableArray array]; - - const Boolean isPlanar = CVPixelBufferIsPlanar(pixelBuffer); - size_t planeCount; - if (isPlanar) { - planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); - } else { - planeCount = 1; - } - - for (int i = 0; i < planeCount; i++) { - void *planeAddress; - size_t bytesPerRow; - size_t height; - size_t width; - - if (isPlanar) { - planeAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, i); - bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, i); - height = CVPixelBufferGetHeightOfPlane(pixelBuffer, i); - width = CVPixelBufferGetWidthOfPlane(pixelBuffer, i); - } else { - planeAddress = CVPixelBufferGetBaseAddress(pixelBuffer); - bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); - height = CVPixelBufferGetHeight(pixelBuffer); - width = CVPixelBufferGetWidth(pixelBuffer); - } - - NSNumber *length = @(bytesPerRow * height); - NSData *bytes = [NSData dataWithBytes:planeAddress length:length.unsignedIntegerValue]; - - NSMutableDictionary *planeBuffer = [NSMutableDictionary dictionary]; - planeBuffer[@"bytesPerRow"] = @(bytesPerRow); - planeBuffer[@"width"] = @(width); - planeBuffer[@"height"] = @(height); - planeBuffer[@"bytes"] = [FlutterStandardTypedData typedDataWithBytes:bytes]; - - [planes addObject:planeBuffer]; - } - // Lock the base address before accessing pixel data, and unlock it afterwards. - // Done accessing the `pixelBuffer` at this point. - CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); - - NSMutableDictionary *imageBuffer = [NSMutableDictionary dictionary]; - imageBuffer[@"width"] = [NSNumber numberWithUnsignedLong:imageWidth]; - imageBuffer[@"height"] = [NSNumber numberWithUnsignedLong:imageHeight]; - imageBuffer[@"format"] = @(_videoFormat); - imageBuffer[@"planes"] = planes; - imageBuffer[@"lensAperture"] = [NSNumber numberWithFloat:[_captureDevice lensAperture]]; - Float64 exposureDuration = CMTimeGetSeconds([_captureDevice exposureDuration]); - Float64 nsExposureDuration = 1000000000 * exposureDuration; - imageBuffer[@"sensorExposureTime"] = [NSNumber numberWithInt:nsExposureDuration]; - imageBuffer[@"sensorSensitivity"] = [NSNumber numberWithFloat:[_captureDevice ISO]]; - - dispatch_async(dispatch_get_main_queue(), ^{ - eventSink(imageBuffer); - }); - } - } - if (_isRecording && !_isRecordingPaused) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [self reportErrorMessage:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - return; - } - - // ignore audio samples until the first video sample arrives to avoid black frames - // https://github.com/flutter/flutter/issues/57831 - if (_isFirstVideoSample && output != _captureVideoOutput.avOutput) { - return; - } - - CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); - - if (_isFirstVideoSample) { - [_videoWriter startSessionAtSourceTime:currentSampleTime]; - // fix sample times not being numeric when pause/resume happens before first sample buffer - // arrives - // https://github.com/flutter/flutter/issues/132014 - _lastVideoSampleTime = currentSampleTime; - _lastAudioSampleTime = currentSampleTime; - _isFirstVideoSample = NO; - } - - if (output == _captureVideoOutput.avOutput) { - if (_videoIsDisconnected) { - _videoIsDisconnected = NO; - - if (_videoTimeOffset.value == 0) { - _videoTimeOffset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); - } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); - _videoTimeOffset = CMTimeAdd(_videoTimeOffset, offset); - } - - return; - } - - _lastVideoSampleTime = currentSampleTime; - - CVPixelBufferRef nextBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); - CMTime nextSampleTime = CMTimeSubtract(_lastVideoSampleTime, _videoTimeOffset); - // do not append sample buffer when readyForMoreMediaData is NO to avoid crash - // https://github.com/flutter/flutter/issues/132073 - if (_videoWriterInput.readyForMoreMediaData) { - [_videoAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextSampleTime]; - } - } else { - CMTime dur = CMSampleBufferGetDuration(sampleBuffer); - - if (dur.value > 0) { - currentSampleTime = CMTimeAdd(currentSampleTime, dur); - } - - if (_audioIsDisconnected) { - _audioIsDisconnected = NO; - - if (_audioTimeOffset.value == 0) { - _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - } else { - CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); - _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); - } - - return; - } - - _lastAudioSampleTime = currentSampleTime; - - if (_audioTimeOffset.value != 0) { - CMSampleBufferRef adjustedSampleBuffer = - [self copySampleBufferWithAdjustedTime:sampleBuffer by:_audioTimeOffset]; - [self newAudioSample:adjustedSampleBuffer]; - CFRelease(adjustedSampleBuffer); - } else { - [self newAudioSample:sampleBuffer]; - } - } - } -} - -- (CMSampleBufferRef)copySampleBufferWithAdjustedTime:(CMSampleBufferRef)sample by:(CMTime)offset { - CMItemCount count; - CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); - CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); - CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); - for (CMItemCount i = 0; i < count; i++) { - pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); - pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); - } - CMSampleBufferRef sout; - CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); - free(pInfo); - return sout; -} - -- (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [self reportErrorMessage:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; - } - if (_videoWriterInput.readyForMoreMediaData) { - if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { - [self reportErrorMessage:@"Unable to write to video input"]; - } - } -} - -- (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { - if (_videoWriter.status != AVAssetWriterStatusWriting) { - if (_videoWriter.status == AVAssetWriterStatusFailed) { - [self reportErrorMessage:[NSString stringWithFormat:@"%@", _videoWriter.error]]; - } - return; - } - if (_audioWriterInput.readyForMoreMediaData) { - if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { - [self reportErrorMessage:@"Unable to write to audio input"]; - } - } -} - - (void)close { [self stop]; for (AVCaptureInput *input in [_videoCaptureSession inputs]) { @@ -749,23 +495,9 @@ - (void)close { } - (void)dealloc { - if (_latestPixelBuffer) { - CFRelease(_latestPixelBuffer); - } [_motionManager stopAccelerometerUpdates]; } -- (CVPixelBufferRef)copyPixelBuffer { - __block CVPixelBufferRef pixelBuffer = nil; - // Use `dispatch_sync` because `copyPixelBuffer` API requires synchronous return. - dispatch_sync(self.pixelBufferSynchronizationQueue, ^{ - // No need weak self because it's dispatch_sync. - pixelBuffer = self.latestPixelBuffer; - self.latestPixelBuffer = nil; - }); - return pixelBuffer; -} - - (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))completion messengerForStreaming:(nullable NSObject *)messenger { if (!_isRecording) { diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h index 0283d79d8c2..4724009fe5c 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h @@ -11,20 +11,21 @@ #import "FLTCamMediaSettingsAVWrapper.h" #import "FLTCaptureDevice.h" #import "FLTDeviceOrientationProviding.h" +#import "FLTImageStreamHandler.h" #import "messages.g.h" NS_ASSUME_NONNULL_BEGIN /// A class that manages camera's state and performs camera operations. -@interface FLTCam : NSObject +@interface FLTCam : NSObject @property(readonly, nonatomic) NSObject *captureDevice; @property(readonly, nonatomic) CGSize previewSize; @property(assign, nonatomic) BOOL isPreviewPaused; -@property(nonatomic, copy) void (^onFrameAvailable)(void); +@property(nonatomic, copy, nullable) void (^onFrameAvailable)(void); /// The API instance used to communicate with the Dart side of the plugin. Once initially set, this /// should only ever be accessed on the main thread. -@property(nonatomic) FCPCameraEventApi *dartAPI; +@property(nonatomic, nullable) FCPCameraEventApi *dartAPI; // Format used for video and image streaming. @property(assign, nonatomic) FourCharCode videoFormat; @property(assign, nonatomic) FCPPlatformImageFileFormat fileFormat; @@ -33,6 +34,22 @@ NS_ASSUME_NONNULL_BEGIN @property(readonly, nonatomic) CGFloat minimumExposureOffset; @property(readonly, nonatomic) CGFloat maximumExposureOffset; +// Properties exposed for the Swift DefaultCamera subclass +@property(nonatomic, nullable) FLTImageStreamHandler *imageStreamHandler; +/// Number of frames currently pending processing. +@property(assign, nonatomic) int streamingPendingFramesCount; +@property(assign, nonatomic) BOOL isFirstVideoSample; +@property(assign, nonatomic) BOOL isRecording; +@property(assign, nonatomic) BOOL isRecordingPaused; +@property(strong, nonatomic, nullable) NSObject *videoWriter; +@property(assign, nonatomic) BOOL videoIsDisconnected; +@property(assign, nonatomic) BOOL audioIsDisconnected; +@property(assign, nonatomic) CMTime videoTimeOffset; +@property(assign, nonatomic) CMTime audioTimeOffset; +@property(strong, nonatomic, nullable) NSObject *videoWriterInput; +@property(strong, nonatomic, nullable) NSObject *audioWriterInput; +@property(nullable) NSObject *videoAdaptor; + /// Initializes an `FLTCam` instance with the given configuration. /// @param error report to the caller if any error happened creating the camera. - (instancetype)initWithConfiguration:(FLTCamConfiguration *)configuration error:(NSError **)error; diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam_Test.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam_Test.h index 0eed426d3c8..e7bfdb55223 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam_Test.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam_Test.h @@ -31,13 +31,6 @@ @property(readonly, nonatomic) NSMutableDictionary *inProgressSavePhotoDelegates; -/// Delegate callback when receiving a new video or audio sample. -/// Exposed for unit tests. -- (void)captureOutput:(AVCaptureOutput *)output - didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer - fromConnection:(AVCaptureConnection *)connection - NS_SWIFT_NAME(captureOutput(_:didOutput:from:)); - /// Start streaming images. - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler; diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index b7bbb4e5510..e0441452e23 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.19+1 +version: 0.9.19+2 environment: sdk: ^3.6.0 diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index c204e83cbc3..1c7b9366fb1 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + +- Route classes now required to use a mixin `with _$RouteName`. + ## 2.9.1 - Fixes an deprecated warning for using withNullability diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 5d90b22f285..6472f56e1d5 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -86,7 +86,7 @@ method. ```dart -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override @@ -108,7 +108,7 @@ The tree of routes is defined as an attribute on each of the top-level routes: ), ], ) -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override @@ -124,7 +124,7 @@ class RedirectRoute extends GoRouteData { } @TypedGoRoute(path: '/login') -class LoginRoute extends GoRouteData { +class LoginRoute extends GoRouteData with _$LoginRoute { LoginRoute({this.from}); final String? from; @@ -211,7 +211,7 @@ Parameters (named or positional) not listed in the path of `TypedGoRoute` indica ```dart @TypedGoRoute(path: '/login') -class LoginRoute extends GoRouteData { +class LoginRoute extends GoRouteData with _$LoginRoute { LoginRoute({this.from}); final String? from; @@ -229,7 +229,7 @@ For query parameters with a **non-nullable** type, you can define a default valu ```dart @TypedGoRoute(path: '/my-route') -class MyRoute extends GoRouteData { +class MyRoute extends GoRouteData with _$MyRoute { MyRoute({this.queryParameter = 'defaultValue'}); final String queryParameter; @@ -250,7 +250,7 @@ parameter with the special name `$extra`: ```dart -class PersonRouteWithExtra extends GoRouteData { +class PersonRouteWithExtra extends GoRouteData with _$PersonRouteWithExtra { PersonRouteWithExtra(this.$extra); final Person? $extra; @@ -281,7 +281,8 @@ You can, of course, combine the use of path, query and $extra parameters: ```dart @TypedGoRoute(path: '/:ketchup') -class HotdogRouteWithEverything extends GoRouteData { +class HotdogRouteWithEverything extends GoRouteData + with _$HotdogRouteWithEverything { HotdogRouteWithEverything(this.ketchup, this.mustard, this.$extra); final bool ketchup; // A required path parameter. final String? mustard; // An optional query parameter. diff --git a/packages/go_router_builder/example/lib/all_types.dart b/packages/go_router_builder/example/lib/all_types.dart index 63d73caa4c0..2394a5e3715 100644 --- a/packages/go_router_builder/example/lib/all_types.dart +++ b/packages/go_router_builder/example/lib/all_types.dart @@ -29,7 +29,7 @@ part 'all_types.g.dart'; path: 'iterable-route-with-default-values'), ]) @immutable -class AllTypesBaseRoute extends GoRouteData { +class AllTypesBaseRoute extends GoRouteData with _$AllTypesBaseRoute { const AllTypesBaseRoute(); @override @@ -39,7 +39,7 @@ class AllTypesBaseRoute extends GoRouteData { ); } -class BigIntRoute extends GoRouteData { +class BigIntRoute extends GoRouteData with _$BigIntRoute { BigIntRoute({ required this.requiredBigIntField, this.bigIntField, @@ -62,7 +62,7 @@ class BigIntRoute extends GoRouteData { ); } -class BoolRoute extends GoRouteData { +class BoolRoute extends GoRouteData with _$BoolRoute { BoolRoute({ required this.requiredBoolField, this.boolField, @@ -88,7 +88,7 @@ class BoolRoute extends GoRouteData { ); } -class DateTimeRoute extends GoRouteData { +class DateTimeRoute extends GoRouteData with _$DateTimeRoute { DateTimeRoute({ required this.requiredDateTimeField, this.dateTimeField, @@ -111,7 +111,7 @@ class DateTimeRoute extends GoRouteData { ); } -class DoubleRoute extends GoRouteData { +class DoubleRoute extends GoRouteData with _$DoubleRoute { DoubleRoute({ required this.requiredDoubleField, this.doubleField, @@ -137,7 +137,7 @@ class DoubleRoute extends GoRouteData { ); } -class IntRoute extends GoRouteData { +class IntRoute extends GoRouteData with _$IntRoute { IntRoute({ required this.requiredIntField, this.intField, @@ -163,7 +163,7 @@ class IntRoute extends GoRouteData { ); } -class NumRoute extends GoRouteData { +class NumRoute extends GoRouteData with _$NumRoute { NumRoute({ required this.requiredNumField, this.numField, @@ -189,7 +189,7 @@ class NumRoute extends GoRouteData { ); } -class EnumRoute extends GoRouteData { +class EnumRoute extends GoRouteData with _$EnumRoute { EnumRoute({ required this.requiredEnumField, this.enumField, @@ -216,7 +216,7 @@ class EnumRoute extends GoRouteData { ); } -class EnhancedEnumRoute extends GoRouteData { +class EnhancedEnumRoute extends GoRouteData with _$EnhancedEnumRoute { EnhancedEnumRoute({ required this.requiredEnumField, this.enumField, @@ -243,7 +243,7 @@ class EnhancedEnumRoute extends GoRouteData { ); } -class StringRoute extends GoRouteData { +class StringRoute extends GoRouteData with _$StringRoute { StringRoute({ required this.requiredStringField, this.stringField, @@ -269,7 +269,7 @@ class StringRoute extends GoRouteData { ); } -class UriRoute extends GoRouteData { +class UriRoute extends GoRouteData with _$UriRoute { UriRoute({ required this.requiredUriField, this.uriField, @@ -292,7 +292,7 @@ class UriRoute extends GoRouteData { ); } -class IterableRoute extends GoRouteData { +class IterableRoute extends GoRouteData with _$IterableRoute { IterableRoute({ this.intIterableField, this.doubleIterableField, @@ -365,7 +365,8 @@ class IterableRoute extends GoRouteData { ); } -class IterableRouteWithDefaultValues extends GoRouteData { +class IterableRouteWithDefaultValues extends GoRouteData + with _$IterableRouteWithDefaultValues { const IterableRouteWithDefaultValues({ this.intIterableField = const [0], this.doubleIterableField = const [0, 1, 2], diff --git a/packages/go_router_builder/example/lib/all_types.g.dart b/packages/go_router_builder/example/lib/all_types.g.dart index 581814d47c2..6af36b14ee6 100644 --- a/packages/go_router_builder/example/lib/all_types.g.dart +++ b/packages/go_router_builder/example/lib/all_types.g.dart @@ -14,82 +14,87 @@ List get $appRoutes => [ RouteBase get $allTypesBaseRoute => GoRouteData.$route( path: '/', - factory: $AllTypesBaseRouteExtension._fromState, + factory: _$AllTypesBaseRoute._fromState, routes: [ GoRouteData.$route( path: 'big-int-route/:requiredBigIntField', - factory: $BigIntRouteExtension._fromState, + factory: _$BigIntRoute._fromState, ), GoRouteData.$route( path: 'bool-route/:requiredBoolField', - factory: $BoolRouteExtension._fromState, + factory: _$BoolRoute._fromState, ), GoRouteData.$route( path: 'date-time-route/:requiredDateTimeField', - factory: $DateTimeRouteExtension._fromState, + factory: _$DateTimeRoute._fromState, ), GoRouteData.$route( path: 'double-route/:requiredDoubleField', - factory: $DoubleRouteExtension._fromState, + factory: _$DoubleRoute._fromState, ), GoRouteData.$route( path: 'int-route/:requiredIntField', - factory: $IntRouteExtension._fromState, + factory: _$IntRoute._fromState, ), GoRouteData.$route( path: 'num-route/:requiredNumField', - factory: $NumRouteExtension._fromState, + factory: _$NumRoute._fromState, ), GoRouteData.$route( path: 'double-route/:requiredDoubleField', - factory: $DoubleRouteExtension._fromState, + factory: _$DoubleRoute._fromState, ), GoRouteData.$route( path: 'enum-route/:requiredEnumField', - factory: $EnumRouteExtension._fromState, + factory: _$EnumRoute._fromState, ), GoRouteData.$route( path: 'enhanced-enum-route/:requiredEnumField', - factory: $EnhancedEnumRouteExtension._fromState, + factory: _$EnhancedEnumRoute._fromState, ), GoRouteData.$route( path: 'string-route/:requiredStringField', - factory: $StringRouteExtension._fromState, + factory: _$StringRoute._fromState, ), GoRouteData.$route( path: 'uri-route/:requiredUriField', - factory: $UriRouteExtension._fromState, + factory: _$UriRoute._fromState, ), GoRouteData.$route( path: 'iterable-route', - factory: $IterableRouteExtension._fromState, + factory: _$IterableRoute._fromState, ), GoRouteData.$route( path: 'iterable-route-with-default-values', - factory: $IterableRouteWithDefaultValuesExtension._fromState, + factory: _$IterableRouteWithDefaultValues._fromState, ), ], ); -extension $AllTypesBaseRouteExtension on AllTypesBaseRoute { +mixin _$AllTypesBaseRoute on GoRouteData { static AllTypesBaseRoute _fromState(GoRouterState state) => const AllTypesBaseRoute(); + @override String get location => GoRouteData.$location( '/', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $BigIntRouteExtension on BigIntRoute { +mixin _$BigIntRoute on GoRouteData { static BigIntRoute _fromState(GoRouterState state) => BigIntRoute( requiredBigIntField: BigInt.parse(state.pathParameters['requiredBigIntField']!)!, @@ -97,24 +102,32 @@ extension $BigIntRouteExtension on BigIntRoute { 'big-int-field', state.uri.queryParameters, BigInt.tryParse), ); + BigIntRoute get _self => this as BigIntRoute; + + @override String get location => GoRouteData.$location( - '/big-int-route/${Uri.encodeComponent(requiredBigIntField.toString())}', + '/big-int-route/${Uri.encodeComponent(_self.requiredBigIntField.toString())}', queryParams: { - if (bigIntField != null) 'big-int-field': bigIntField!.toString(), + if (_self.bigIntField != null) + 'big-int-field': _self.bigIntField!.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $BoolRouteExtension on BoolRoute { +mixin _$BoolRoute on GoRouteData { static BoolRoute _fromState(GoRouterState state) => BoolRoute( requiredBoolField: _$boolConverter(state.pathParameters['requiredBoolField']!)!, @@ -127,27 +140,35 @@ extension $BoolRouteExtension on BoolRoute { true, ); + BoolRoute get _self => this as BoolRoute; + + @override String get location => GoRouteData.$location( - '/bool-route/${Uri.encodeComponent(requiredBoolField.toString())}', + '/bool-route/${Uri.encodeComponent(_self.requiredBoolField.toString())}', queryParams: { - if (boolField != null) 'bool-field': boolField!.toString(), - if (boolFieldWithDefaultValue != true) + if (_self.boolField != null) + 'bool-field': _self.boolField!.toString(), + if (_self.boolFieldWithDefaultValue != true) 'bool-field-with-default-value': - boolFieldWithDefaultValue.toString(), + _self.boolFieldWithDefaultValue.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $DateTimeRouteExtension on DateTimeRoute { +mixin _$DateTimeRoute on GoRouteData { static DateTimeRoute _fromState(GoRouterState state) => DateTimeRoute( requiredDateTimeField: DateTime.parse(state.pathParameters['requiredDateTimeField']!)!, @@ -155,25 +176,32 @@ extension $DateTimeRouteExtension on DateTimeRoute { 'date-time-field', state.uri.queryParameters, DateTime.tryParse), ); + DateTimeRoute get _self => this as DateTimeRoute; + + @override String get location => GoRouteData.$location( - '/date-time-route/${Uri.encodeComponent(requiredDateTimeField.toString())}', + '/date-time-route/${Uri.encodeComponent(_self.requiredDateTimeField.toString())}', queryParams: { - if (dateTimeField != null) - 'date-time-field': dateTimeField!.toString(), + if (_self.dateTimeField != null) + 'date-time-field': _self.dateTimeField!.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $DoubleRouteExtension on DoubleRoute { +mixin _$DoubleRoute on GoRouteData { static DoubleRoute _fromState(GoRouterState state) => DoubleRoute( requiredDoubleField: double.parse(state.pathParameters['requiredDoubleField']!)!, @@ -186,27 +214,35 @@ extension $DoubleRouteExtension on DoubleRoute { 1.0, ); + DoubleRoute get _self => this as DoubleRoute; + + @override String get location => GoRouteData.$location( - '/double-route/${Uri.encodeComponent(requiredDoubleField.toString())}', + '/double-route/${Uri.encodeComponent(_self.requiredDoubleField.toString())}', queryParams: { - if (doubleField != null) 'double-field': doubleField!.toString(), - if (doubleFieldWithDefaultValue != 1.0) + if (_self.doubleField != null) + 'double-field': _self.doubleField!.toString(), + if (_self.doubleFieldWithDefaultValue != 1.0) 'double-field-with-default-value': - doubleFieldWithDefaultValue.toString(), + _self.doubleFieldWithDefaultValue.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $IntRouteExtension on IntRoute { +mixin _$IntRoute on GoRouteData { static IntRoute _fromState(GoRouterState state) => IntRoute( requiredIntField: int.parse(state.pathParameters['requiredIntField']!)!, intField: _$convertMapValue( @@ -218,26 +254,34 @@ extension $IntRouteExtension on IntRoute { 1, ); + IntRoute get _self => this as IntRoute; + + @override String get location => GoRouteData.$location( - '/int-route/${Uri.encodeComponent(requiredIntField.toString())}', + '/int-route/${Uri.encodeComponent(_self.requiredIntField.toString())}', queryParams: { - if (intField != null) 'int-field': intField!.toString(), - if (intFieldWithDefaultValue != 1) - 'int-field-with-default-value': intFieldWithDefaultValue.toString(), + if (_self.intField != null) 'int-field': _self.intField!.toString(), + if (_self.intFieldWithDefaultValue != 1) + 'int-field-with-default-value': + _self.intFieldWithDefaultValue.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $NumRouteExtension on NumRoute { +mixin _$NumRoute on GoRouteData { static NumRoute _fromState(GoRouterState state) => NumRoute( requiredNumField: num.parse(state.pathParameters['requiredNumField']!)!, numField: _$convertMapValue( @@ -249,26 +293,34 @@ extension $NumRouteExtension on NumRoute { 1, ); + NumRoute get _self => this as NumRoute; + + @override String get location => GoRouteData.$location( - '/num-route/${Uri.encodeComponent(requiredNumField.toString())}', + '/num-route/${Uri.encodeComponent(_self.requiredNumField.toString())}', queryParams: { - if (numField != null) 'num-field': numField!.toString(), - if (numFieldWithDefaultValue != 1) - 'num-field-with-default-value': numFieldWithDefaultValue.toString(), + if (_self.numField != null) 'num-field': _self.numField!.toString(), + if (_self.numFieldWithDefaultValue != 1) + 'num-field-with-default-value': + _self.numFieldWithDefaultValue.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $EnumRouteExtension on EnumRoute { +mixin _$EnumRoute on GoRouteData { static EnumRoute _fromState(GoRouterState state) => EnumRoute( requiredEnumField: _$PersonDetailsEnumMap ._$fromName(state.pathParameters['requiredEnumField']!)!, @@ -281,24 +333,31 @@ extension $EnumRouteExtension on EnumRoute { PersonDetails.favoriteFood, ); + EnumRoute get _self => this as EnumRoute; + + @override String get location => GoRouteData.$location( - '/enum-route/${Uri.encodeComponent(_$PersonDetailsEnumMap[requiredEnumField]!)}', + '/enum-route/${Uri.encodeComponent(_$PersonDetailsEnumMap[_self.requiredEnumField]!)}', queryParams: { - if (enumField != null) - 'enum-field': _$PersonDetailsEnumMap[enumField!], - if (enumFieldWithDefaultValue != PersonDetails.favoriteFood) + if (_self.enumField != null) + 'enum-field': _$PersonDetailsEnumMap[_self.enumField!], + if (_self.enumFieldWithDefaultValue != PersonDetails.favoriteFood) 'enum-field-with-default-value': - _$PersonDetailsEnumMap[enumFieldWithDefaultValue], + _$PersonDetailsEnumMap[_self.enumFieldWithDefaultValue], }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } @@ -308,7 +367,7 @@ const _$PersonDetailsEnumMap = { PersonDetails.favoriteSport: 'favorite-sport', }; -extension $EnhancedEnumRouteExtension on EnhancedEnumRoute { +mixin _$EnhancedEnumRoute on GoRouteData { static EnhancedEnumRoute _fromState(GoRouterState state) => EnhancedEnumRoute( requiredEnumField: _$SportDetailsEnumMap ._$fromName(state.pathParameters['requiredEnumField']!)!, @@ -321,24 +380,31 @@ extension $EnhancedEnumRouteExtension on EnhancedEnumRoute { SportDetails.football, ); + EnhancedEnumRoute get _self => this as EnhancedEnumRoute; + + @override String get location => GoRouteData.$location( - '/enhanced-enum-route/${Uri.encodeComponent(_$SportDetailsEnumMap[requiredEnumField]!)}', + '/enhanced-enum-route/${Uri.encodeComponent(_$SportDetailsEnumMap[_self.requiredEnumField]!)}', queryParams: { - if (enumField != null) - 'enum-field': _$SportDetailsEnumMap[enumField!], - if (enumFieldWithDefaultValue != SportDetails.football) + if (_self.enumField != null) + 'enum-field': _$SportDetailsEnumMap[_self.enumField!], + if (_self.enumFieldWithDefaultValue != SportDetails.football) 'enum-field-with-default-value': - _$SportDetailsEnumMap[enumFieldWithDefaultValue], + _$SportDetailsEnumMap[_self.enumFieldWithDefaultValue], }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } @@ -349,7 +415,7 @@ const _$SportDetailsEnumMap = { SportDetails.hockey: 'hockey', }; -extension $StringRouteExtension on StringRoute { +mixin _$StringRoute on GoRouteData { static StringRoute _fromState(GoRouterState state) => StringRoute( requiredStringField: state.pathParameters['requiredStringField']!, stringField: state.uri.queryParameters['string-field'], @@ -358,50 +424,65 @@ extension $StringRouteExtension on StringRoute { 'defaultValue', ); + StringRoute get _self => this as StringRoute; + + @override String get location => GoRouteData.$location( - '/string-route/${Uri.encodeComponent(requiredStringField)}', + '/string-route/${Uri.encodeComponent(_self.requiredStringField)}', queryParams: { - if (stringField != null) 'string-field': stringField, - if (stringFieldWithDefaultValue != 'defaultValue') - 'string-field-with-default-value': stringFieldWithDefaultValue, + if (_self.stringField != null) 'string-field': _self.stringField, + if (_self.stringFieldWithDefaultValue != 'defaultValue') + 'string-field-with-default-value': + _self.stringFieldWithDefaultValue, }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $UriRouteExtension on UriRoute { +mixin _$UriRoute on GoRouteData { static UriRoute _fromState(GoRouterState state) => UriRoute( requiredUriField: Uri.parse(state.pathParameters['requiredUriField']!)!, uriField: _$convertMapValue( 'uri-field', state.uri.queryParameters, Uri.tryParse), ); + UriRoute get _self => this as UriRoute; + + @override String get location => GoRouteData.$location( - '/uri-route/${Uri.encodeComponent(requiredUriField.toString())}', + '/uri-route/${Uri.encodeComponent(_self.requiredUriField.toString())}', queryParams: { - if (uriField != null) 'uri-field': uriField!.toString(), + if (_self.uriField != null) 'uri-field': _self.uriField!.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $IterableRouteExtension on IterableRoute { +mixin _$IterableRoute on GoRouteData { static IterableRoute _fromState(GoRouterState state) => IterableRoute( intIterableField: (state.uri.queryParametersAll['int-iterable-field'] ?.map(int.parse) @@ -481,71 +562,84 @@ extension $IterableRouteExtension on IterableRoute { ?.toSet(), ); + IterableRoute get _self => this as IterableRoute; + + @override String get location => GoRouteData.$location( '/iterable-route', queryParams: { - if (intIterableField != null) + if (_self.intIterableField != null) 'int-iterable-field': - intIterableField?.map((e) => e.toString()).toList(), - if (doubleIterableField != null) + _self.intIterableField?.map((e) => e.toString()).toList(), + if (_self.doubleIterableField != null) 'double-iterable-field': - doubleIterableField?.map((e) => e.toString()).toList(), - if (stringIterableField != null) + _self.doubleIterableField?.map((e) => e.toString()).toList(), + if (_self.stringIterableField != null) 'string-iterable-field': - stringIterableField?.map((e) => e).toList(), - if (boolIterableField != null) + _self.stringIterableField?.map((e) => e).toList(), + if (_self.boolIterableField != null) 'bool-iterable-field': - boolIterableField?.map((e) => e.toString()).toList(), - if (enumIterableField != null) - 'enum-iterable-field': enumIterableField + _self.boolIterableField?.map((e) => e.toString()).toList(), + if (_self.enumIterableField != null) + 'enum-iterable-field': _self.enumIterableField ?.map((e) => _$SportDetailsEnumMap[e]) .toList(), - if (enumOnlyInIterableField != null) - 'enum-only-in-iterable-field': enumOnlyInIterableField + if (_self.enumOnlyInIterableField != null) + 'enum-only-in-iterable-field': _self.enumOnlyInIterableField ?.map((e) => _$CookingRecipeEnumMap[e]) .toList(), - if (intListField != null) - 'int-list-field': intListField?.map((e) => e.toString()).toList(), - if (doubleListField != null) + if (_self.intListField != null) + 'int-list-field': + _self.intListField?.map((e) => e.toString()).toList(), + if (_self.doubleListField != null) 'double-list-field': - doubleListField?.map((e) => e.toString()).toList(), - if (stringListField != null) - 'string-list-field': stringListField?.map((e) => e).toList(), - if (boolListField != null) - 'bool-list-field': boolListField?.map((e) => e.toString()).toList(), - if (enumListField != null) - 'enum-list-field': - enumListField?.map((e) => _$SportDetailsEnumMap[e]).toList(), - if (enumOnlyInListField != null) - 'enum-only-in-list-field': enumOnlyInListField + _self.doubleListField?.map((e) => e.toString()).toList(), + if (_self.stringListField != null) + 'string-list-field': _self.stringListField?.map((e) => e).toList(), + if (_self.boolListField != null) + 'bool-list-field': + _self.boolListField?.map((e) => e.toString()).toList(), + if (_self.enumListField != null) + 'enum-list-field': _self.enumListField + ?.map((e) => _$SportDetailsEnumMap[e]) + .toList(), + if (_self.enumOnlyInListField != null) + 'enum-only-in-list-field': _self.enumOnlyInListField ?.map((e) => _$CookingRecipeEnumMap[e]) .toList(), - if (intSetField != null) - 'int-set-field': intSetField?.map((e) => e.toString()).toList(), - if (doubleSetField != null) + if (_self.intSetField != null) + 'int-set-field': + _self.intSetField?.map((e) => e.toString()).toList(), + if (_self.doubleSetField != null) 'double-set-field': - doubleSetField?.map((e) => e.toString()).toList(), - if (stringSetField != null) - 'string-set-field': stringSetField?.map((e) => e).toList(), - if (boolSetField != null) - 'bool-set-field': boolSetField?.map((e) => e.toString()).toList(), - if (enumSetField != null) - 'enum-set-field': - enumSetField?.map((e) => _$SportDetailsEnumMap[e]).toList(), - if (enumOnlyInSetField != null) - 'enum-only-in-set-field': enumOnlyInSetField + _self.doubleSetField?.map((e) => e.toString()).toList(), + if (_self.stringSetField != null) + 'string-set-field': _self.stringSetField?.map((e) => e).toList(), + if (_self.boolSetField != null) + 'bool-set-field': + _self.boolSetField?.map((e) => e.toString()).toList(), + if (_self.enumSetField != null) + 'enum-set-field': _self.enumSetField + ?.map((e) => _$SportDetailsEnumMap[e]) + .toList(), + if (_self.enumOnlyInSetField != null) + 'enum-only-in-set-field': _self.enumOnlyInSetField ?.map((e) => _$CookingRecipeEnumMap[e]) .toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } @@ -555,8 +649,7 @@ const _$CookingRecipeEnumMap = { CookingRecipe.tacos: 'tacos', }; -extension $IterableRouteWithDefaultValuesExtension - on IterableRouteWithDefaultValues { +mixin _$IterableRouteWithDefaultValues on GoRouteData { static IterableRouteWithDefaultValues _fromState(GoRouterState state) => IterableRouteWithDefaultValues( intIterableField: (state.uri.queryParametersAll['int-iterable-field'] @@ -636,62 +729,80 @@ extension $IterableRouteWithDefaultValuesExtension const {SportDetails.hockey}, ); + IterableRouteWithDefaultValues get _self => + this as IterableRouteWithDefaultValues; + + @override String get location => GoRouteData.$location( '/iterable-route-with-default-values', queryParams: { - if (!_$iterablesEqual(intIterableField, const [0])) + if (!_$iterablesEqual(_self.intIterableField, const [0])) 'int-iterable-field': - intIterableField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(doubleIterableField, const [0, 1, 2])) + _self.intIterableField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual( + _self.doubleIterableField, const [0, 1, 2])) 'double-iterable-field': - doubleIterableField.map((e) => e.toString()).toList(), + _self.doubleIterableField.map((e) => e.toString()).toList(), if (!_$iterablesEqual( - stringIterableField, const ['defaultValue'])) - 'string-iterable-field': stringIterableField.map((e) => e).toList(), - if (!_$iterablesEqual(boolIterableField, const [false])) + _self.stringIterableField, const ['defaultValue'])) + 'string-iterable-field': + _self.stringIterableField.map((e) => e).toList(), + if (!_$iterablesEqual(_self.boolIterableField, const [false])) 'bool-iterable-field': - boolIterableField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(enumIterableField, + _self.boolIterableField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.enumIterableField, const [SportDetails.tennis, SportDetails.hockey])) - 'enum-iterable-field': - enumIterableField.map((e) => _$SportDetailsEnumMap[e]).toList(), - if (!_$iterablesEqual(intListField, const [0])) - 'int-list-field': intListField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(doubleListField, const [1, 2, 3])) + 'enum-iterable-field': _self.enumIterableField + .map((e) => _$SportDetailsEnumMap[e]) + .toList(), + if (!_$iterablesEqual(_self.intListField, const [0])) + 'int-list-field': + _self.intListField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.doubleListField, const [1, 2, 3])) 'double-list-field': - doubleListField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(stringListField, + _self.doubleListField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.stringListField, const ['defaultValue0', 'defaultValue1'])) - 'string-list-field': stringListField.map((e) => e).toList(), - if (!_$iterablesEqual(boolListField, const [true])) - 'bool-list-field': boolListField.map((e) => e.toString()).toList(), + 'string-list-field': _self.stringListField.map((e) => e).toList(), + if (!_$iterablesEqual(_self.boolListField, const [true])) + 'bool-list-field': + _self.boolListField.map((e) => e.toString()).toList(), if (!_$iterablesEqual( - enumListField, const [SportDetails.football])) - 'enum-list-field': - enumListField.map((e) => _$SportDetailsEnumMap[e]).toList(), - if (!_$iterablesEqual(intSetField, const {0, 1})) - 'int-set-field': intSetField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(doubleSetField, const {})) + _self.enumListField, const [SportDetails.football])) + 'enum-list-field': _self.enumListField + .map((e) => _$SportDetailsEnumMap[e]) + .toList(), + if (!_$iterablesEqual(_self.intSetField, const {0, 1})) + 'int-set-field': + _self.intSetField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.doubleSetField, const {})) 'double-set-field': - doubleSetField.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(stringSetField, const {'defaultValue'})) - 'string-set-field': stringSetField.map((e) => e).toList(), - if (!_$iterablesEqual(boolSetField, const {true, false})) - 'bool-set-field': boolSetField.map((e) => e.toString()).toList(), + _self.doubleSetField.map((e) => e.toString()).toList(), if (!_$iterablesEqual( - enumSetField, const {SportDetails.hockey})) - 'enum-set-field': - enumSetField.map((e) => _$SportDetailsEnumMap[e]).toList(), + _self.stringSetField, const {'defaultValue'})) + 'string-set-field': _self.stringSetField.map((e) => e).toList(), + if (!_$iterablesEqual(_self.boolSetField, const {true, false})) + 'bool-set-field': + _self.boolSetField.map((e) => e.toString()).toList(), + if (!_$iterablesEqual( + _self.enumSetField, const {SportDetails.hockey})) + 'enum-set-field': _self.enumSetField + .map((e) => _$SportDetailsEnumMap[e]) + .toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/case_sensitive_example.dart b/packages/go_router_builder/example/lib/case_sensitive_example.dart index 579514064ea..65d82e8790f 100644 --- a/packages/go_router_builder/example/lib/case_sensitive_example.dart +++ b/packages/go_router_builder/example/lib/case_sensitive_example.dart @@ -28,7 +28,7 @@ class CaseSensitivityApp extends StatelessWidget { @TypedGoRoute( path: '/case-sensitive', ) -class CaseSensitiveRoute extends GoRouteData { +class CaseSensitiveRoute extends GoRouteData with _$CaseSensitiveRoute { const CaseSensitiveRoute(); @override @@ -41,7 +41,7 @@ class CaseSensitiveRoute extends GoRouteData { path: '/not-case-sensitive', caseSensitive: false, ) -class NotCaseSensitiveRoute extends GoRouteData { +class NotCaseSensitiveRoute extends GoRouteData with _$NotCaseSensitiveRoute { const NotCaseSensitiveRoute(); @override diff --git a/packages/go_router_builder/example/lib/case_sensitive_example.g.dart b/packages/go_router_builder/example/lib/case_sensitive_example.g.dart index e367ea7507c..3c6a5b84e31 100644 --- a/packages/go_router_builder/example/lib/case_sensitive_example.g.dart +++ b/packages/go_router_builder/example/lib/case_sensitive_example.g.dart @@ -15,47 +15,57 @@ List get $appRoutes => [ RouteBase get $caseSensitiveRoute => GoRouteData.$route( path: '/case-sensitive', - factory: $CaseSensitiveRouteExtension._fromState, + factory: _$CaseSensitiveRoute._fromState, ); -extension $CaseSensitiveRouteExtension on CaseSensitiveRoute { +mixin _$CaseSensitiveRoute on GoRouteData { static CaseSensitiveRoute _fromState(GoRouterState state) => const CaseSensitiveRoute(); + @override String get location => GoRouteData.$location( '/case-sensitive', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $notCaseSensitiveRoute => GoRouteData.$route( path: '/not-case-sensitive', caseSensitive: false, - factory: $NotCaseSensitiveRouteExtension._fromState, + factory: _$NotCaseSensitiveRoute._fromState, ); -extension $NotCaseSensitiveRouteExtension on NotCaseSensitiveRoute { +mixin _$NotCaseSensitiveRoute on GoRouteData { static NotCaseSensitiveRoute _fromState(GoRouterState state) => const NotCaseSensitiveRoute(); + @override String get location => GoRouteData.$location( '/not-case-sensitive', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/extra_example.dart b/packages/go_router_builder/example/lib/extra_example.dart index 33547b1a646..5f281cd4e46 100644 --- a/packages/go_router_builder/example/lib/extra_example.dart +++ b/packages/go_router_builder/example/lib/extra_example.dart @@ -34,7 +34,7 @@ class Extra { } @TypedGoRoute(path: '/requiredExtra') -class RequiredExtraRoute extends GoRouteData { +class RequiredExtraRoute extends GoRouteData with _$RequiredExtraRoute { const RequiredExtraRoute({required this.$extra}); final Extra $extra; @@ -59,7 +59,7 @@ class RequiredExtraScreen extends StatelessWidget { } @TypedGoRoute(path: '/optionalExtra') -class OptionalExtraRoute extends GoRouteData { +class OptionalExtraRoute extends GoRouteData with _$OptionalExtraRoute { const OptionalExtraRoute({this.$extra}); final Extra? $extra; @@ -84,7 +84,7 @@ class OptionalExtraScreen extends StatelessWidget { } @TypedGoRoute(path: '/splash') -class SplashRoute extends GoRouteData { +class SplashRoute extends GoRouteData with _$SplashRoute { const SplashRoute(); @override diff --git a/packages/go_router_builder/example/lib/extra_example.g.dart b/packages/go_router_builder/example/lib/extra_example.g.dart index 7c43e54d2e5..1161326b103 100644 --- a/packages/go_router_builder/example/lib/extra_example.g.dart +++ b/packages/go_router_builder/example/lib/extra_example.g.dart @@ -16,76 +16,95 @@ List get $appRoutes => [ RouteBase get $requiredExtraRoute => GoRouteData.$route( path: '/requiredExtra', - factory: $RequiredExtraRouteExtension._fromState, + factory: _$RequiredExtraRoute._fromState, ); -extension $RequiredExtraRouteExtension on RequiredExtraRoute { +mixin _$RequiredExtraRoute on GoRouteData { static RequiredExtraRoute _fromState(GoRouterState state) => RequiredExtraRoute( $extra: state.extra as Extra, ); + RequiredExtraRoute get _self => this as RequiredExtraRoute; + + @override String get location => GoRouteData.$location( '/requiredExtra', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } RouteBase get $optionalExtraRoute => GoRouteData.$route( path: '/optionalExtra', - factory: $OptionalExtraRouteExtension._fromState, + factory: _$OptionalExtraRoute._fromState, ); -extension $OptionalExtraRouteExtension on OptionalExtraRoute { +mixin _$OptionalExtraRoute on GoRouteData { static OptionalExtraRoute _fromState(GoRouterState state) => OptionalExtraRoute( $extra: state.extra as Extra?, ); + OptionalExtraRoute get _self => this as OptionalExtraRoute; + + @override String get location => GoRouteData.$location( '/optionalExtra', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } RouteBase get $splashRoute => GoRouteData.$route( path: '/splash', - factory: $SplashRouteExtension._fromState, + factory: _$SplashRoute._fromState, ); -extension $SplashRouteExtension on SplashRoute { +mixin _$SplashRoute on GoRouteData { static SplashRoute _fromState(GoRouterState state) => const SplashRoute(); + @override String get location => GoRouteData.$location( '/splash', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/main.dart b/packages/go_router_builder/example/lib/main.dart index d555649825a..38136b17a9d 100644 --- a/packages/go_router_builder/example/lib/main.dart +++ b/packages/go_router_builder/example/lib/main.dart @@ -80,7 +80,7 @@ class App extends StatelessWidget { TypedGoRoute(path: 'family-count/:count'), ], ) -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override @@ -90,7 +90,7 @@ class HomeRoute extends GoRouteData { @TypedGoRoute( path: '/login', ) -class LoginRoute extends GoRouteData { +class LoginRoute extends GoRouteData with _$LoginRoute { const LoginRoute({this.fromPage}); final String? fromPage; @@ -100,7 +100,7 @@ class LoginRoute extends GoRouteData { LoginScreen(from: fromPage); } -class FamilyRoute extends GoRouteData { +class FamilyRoute extends GoRouteData with _$FamilyRoute { const FamilyRoute(this.fid); final String fid; @@ -110,7 +110,7 @@ class FamilyRoute extends GoRouteData { FamilyScreen(family: familyById(fid)); } -class PersonRoute extends GoRouteData { +class PersonRoute extends GoRouteData with _$PersonRoute { const PersonRoute(this.fid, this.pid); final String fid; @@ -124,7 +124,7 @@ class PersonRoute extends GoRouteData { } } -class PersonDetailsRoute extends GoRouteData { +class PersonDetailsRoute extends GoRouteData with _$PersonDetailsRoute { const PersonDetailsRoute(this.fid, this.pid, this.details, {this.$extra}); final String fid; @@ -150,7 +150,7 @@ class PersonDetailsRoute extends GoRouteData { } } -class FamilyCountRoute extends GoRouteData { +class FamilyCountRoute extends GoRouteData with _$FamilyCountRoute { const FamilyCountRoute(this.count); final int count; diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index ab4cbdb3540..f631d241cc0 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -15,19 +15,19 @@ List get $appRoutes => [ RouteBase get $homeRoute => GoRouteData.$route( path: '/', - factory: $HomeRouteExtension._fromState, + factory: _$HomeRoute._fromState, routes: [ GoRouteData.$route( path: 'family/:fid', - factory: $FamilyRouteExtension._fromState, + factory: _$FamilyRoute._fromState, routes: [ GoRouteData.$route( path: 'person/:pid', - factory: $PersonRouteExtension._fromState, + factory: _$PersonRoute._fromState, routes: [ GoRouteData.$route( path: 'details/:details', - factory: $PersonDetailsRouteExtension._fromState, + factory: _$PersonDetailsRoute._fromState, ), ], ), @@ -35,68 +35,87 @@ RouteBase get $homeRoute => GoRouteData.$route( ), GoRouteData.$route( path: 'family-count/:count', - factory: $FamilyCountRouteExtension._fromState, + factory: _$FamilyCountRoute._fromState, ), ], ); -extension $HomeRouteExtension on HomeRoute { +mixin _$HomeRoute on GoRouteData { static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + @override String get location => GoRouteData.$location( '/', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $FamilyRouteExtension on FamilyRoute { +mixin _$FamilyRoute on GoRouteData { static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( state.pathParameters['fid']!, ); + FamilyRoute get _self => this as FamilyRoute; + + @override String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}', + '/family/${Uri.encodeComponent(_self.fid)}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $PersonRouteExtension on PersonRoute { +mixin _$PersonRoute on GoRouteData { static PersonRoute _fromState(GoRouterState state) => PersonRoute( state.pathParameters['fid']!, int.parse(state.pathParameters['pid']!)!, ); + PersonRoute get _self => this as PersonRoute; + + @override String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}', + '/family/${Uri.encodeComponent(_self.fid)}/person/${Uri.encodeComponent(_self.pid.toString())}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $PersonDetailsRouteExtension on PersonDetailsRoute { +mixin _$PersonDetailsRoute on GoRouteData { static PersonDetailsRoute _fromState(GoRouterState state) => PersonDetailsRoute( state.pathParameters['fid']!, @@ -105,20 +124,27 @@ extension $PersonDetailsRouteExtension on PersonDetailsRoute { $extra: state.extra as int?, ); + PersonDetailsRoute get _self => this as PersonDetailsRoute; + + @override String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid)}/person/${Uri.encodeComponent(pid.toString())}/details/${Uri.encodeComponent(_$PersonDetailsEnumMap[details]!)}', + '/family/${Uri.encodeComponent(_self.fid)}/person/${Uri.encodeComponent(_self.pid.toString())}/details/${Uri.encodeComponent(_$PersonDetailsEnumMap[_self.details]!)}', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } const _$PersonDetailsEnumMap = { @@ -127,22 +153,29 @@ const _$PersonDetailsEnumMap = { PersonDetails.favoriteSport: 'favorite-sport', }; -extension $FamilyCountRouteExtension on FamilyCountRoute { +mixin _$FamilyCountRoute on GoRouteData { static FamilyCountRoute _fromState(GoRouterState state) => FamilyCountRoute( int.parse(state.pathParameters['count']!)!, ); + FamilyCountRoute get _self => this as FamilyCountRoute; + + @override String get location => GoRouteData.$location( - '/family-count/${Uri.encodeComponent(count.toString())}', + '/family-count/${Uri.encodeComponent(_self.count.toString())}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } @@ -153,27 +186,34 @@ extension on Map { RouteBase get $loginRoute => GoRouteData.$route( path: '/login', - factory: $LoginRouteExtension._fromState, + factory: _$LoginRoute._fromState, ); -extension $LoginRouteExtension on LoginRoute { +mixin _$LoginRoute on GoRouteData { static LoginRoute _fromState(GoRouterState state) => LoginRoute( fromPage: state.uri.queryParameters['from-page'], ); + LoginRoute get _self => this as LoginRoute; + + @override String get location => GoRouteData.$location( '/login', queryParams: { - if (fromPage != null) 'from-page': fromPage, + if (_self.fromPage != null) 'from-page': _self.fromPage, }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/on_exit_example.dart b/packages/go_router_builder/example/lib/on_exit_example.dart index b574c852c6e..bbd8fea19bb 100644 --- a/packages/go_router_builder/example/lib/on_exit_example.dart +++ b/packages/go_router_builder/example/lib/on_exit_example.dart @@ -29,14 +29,14 @@ class App extends StatelessWidget { TypedGoRoute(path: 'sub-route') ], ) -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } -class SubRoute extends GoRouteData { +class SubRoute extends GoRouteData with _$SubRoute { const SubRoute(); @override diff --git a/packages/go_router_builder/example/lib/on_exit_example.g.dart b/packages/go_router_builder/example/lib/on_exit_example.g.dart index 8a99156d8bf..458ccc0371b 100644 --- a/packages/go_router_builder/example/lib/on_exit_example.g.dart +++ b/packages/go_router_builder/example/lib/on_exit_example.g.dart @@ -14,45 +14,55 @@ List get $appRoutes => [ RouteBase get $homeRoute => GoRouteData.$route( path: '/', - factory: $HomeRouteExtension._fromState, + factory: _$HomeRoute._fromState, routes: [ GoRouteData.$route( path: 'sub-route', - factory: $SubRouteExtension._fromState, + factory: _$SubRoute._fromState, ), ], ); -extension $HomeRouteExtension on HomeRoute { +mixin _$HomeRoute on GoRouteData { static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + @override String get location => GoRouteData.$location( '/', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $SubRouteExtension on SubRoute { +mixin _$SubRoute on GoRouteData { static SubRoute _fromState(GoRouterState state) => const SubRoute(); + @override String get location => GoRouteData.$location( '/sub-route', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/readme_excerpts.dart b/packages/go_router_builder/example/lib/readme_excerpts.dart index 28eabdf1fa5..73577ef6452 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.dart @@ -87,7 +87,7 @@ void otherDoc(BuildContext context) { ], ) // #docregion HomeRoute -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override @@ -107,7 +107,7 @@ class RedirectRoute extends GoRouteData { // #docregion login @TypedGoRoute(path: '/login') -class LoginRoute extends GoRouteData { +class LoginRoute extends GoRouteData with _$LoginRoute { LoginRoute({this.from}); final String? from; @@ -142,7 +142,7 @@ class HomeScreen extends StatelessWidget { } } -class FamilyRoute extends GoRouteData { +class FamilyRoute extends GoRouteData with _$FamilyRoute { const FamilyRoute({this.fid}); final String? fid; @@ -218,7 +218,7 @@ class LoginScreen extends StatelessWidget { // #docregion MyRoute @TypedGoRoute(path: '/my-route') -class MyRoute extends GoRouteData { +class MyRoute extends GoRouteData with _$MyRoute { MyRoute({this.queryParameter = 'defaultValue'}); final String queryParameter; @@ -245,7 +245,7 @@ class MyScreen extends StatelessWidget { @TypedGoRoute(path: '/person') // #docregion PersonRouteWithExtra -class PersonRouteWithExtra extends GoRouteData { +class PersonRouteWithExtra extends GoRouteData with _$PersonRouteWithExtra { PersonRouteWithExtra(this.$extra); final Person? $extra; @@ -272,7 +272,8 @@ class PersonScreen extends StatelessWidget { // #docregion HotdogRouteWithEverything @TypedGoRoute(path: '/:ketchup') -class HotdogRouteWithEverything extends GoRouteData { +class HotdogRouteWithEverything extends GoRouteData + with _$HotdogRouteWithEverything { HotdogRouteWithEverything(this.ketchup, this.mustard, this.$extra); final bool ketchup; // A required path parameter. final String? mustard; // An optional query parameter. diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart index f6719844df6..41148c9230f 100644 --- a/packages/go_router_builder/example/lib/readme_excerpts.g.dart +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -18,140 +18,173 @@ List get $appRoutes => [ RouteBase get $homeRoute => GoRouteData.$route( path: '/', - factory: $HomeRouteExtension._fromState, + factory: _$HomeRoute._fromState, routes: [ GoRouteData.$route( path: 'family/:fid', - factory: $FamilyRouteExtension._fromState, + factory: _$FamilyRoute._fromState, ), ], ); -extension $HomeRouteExtension on HomeRoute { +mixin _$HomeRoute on GoRouteData { static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + @override String get location => GoRouteData.$location( '/', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $FamilyRouteExtension on FamilyRoute { +mixin _$FamilyRoute on GoRouteData { static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( fid: state.pathParameters['fid'], ); + FamilyRoute get _self => this as FamilyRoute; + + @override String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(fid ?? '')}', + '/family/${Uri.encodeComponent(_self.fid ?? '')}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $loginRoute => GoRouteData.$route( path: '/login', - factory: $LoginRouteExtension._fromState, + factory: _$LoginRoute._fromState, ); -extension $LoginRouteExtension on LoginRoute { +mixin _$LoginRoute on GoRouteData { static LoginRoute _fromState(GoRouterState state) => LoginRoute( from: state.uri.queryParameters['from'], ); + LoginRoute get _self => this as LoginRoute; + + @override String get location => GoRouteData.$location( '/login', queryParams: { - if (from != null) 'from': from, + if (_self.from != null) 'from': _self.from, }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $myRoute => GoRouteData.$route( path: '/my-route', - factory: $MyRouteExtension._fromState, + factory: _$MyRoute._fromState, ); -extension $MyRouteExtension on MyRoute { +mixin _$MyRoute on GoRouteData { static MyRoute _fromState(GoRouterState state) => MyRoute( queryParameter: state.uri.queryParameters['query-parameter'] ?? 'defaultValue', ); + MyRoute get _self => this as MyRoute; + + @override String get location => GoRouteData.$location( '/my-route', queryParams: { - if (queryParameter != 'defaultValue') - 'query-parameter': queryParameter, + if (_self.queryParameter != 'defaultValue') + 'query-parameter': _self.queryParameter, }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $personRouteWithExtra => GoRouteData.$route( path: '/person', - factory: $PersonRouteWithExtraExtension._fromState, + factory: _$PersonRouteWithExtra._fromState, ); -extension $PersonRouteWithExtraExtension on PersonRouteWithExtra { +mixin _$PersonRouteWithExtra on GoRouteData { static PersonRouteWithExtra _fromState(GoRouterState state) => PersonRouteWithExtra( state.extra as Person?, ); + PersonRouteWithExtra get _self => this as PersonRouteWithExtra; + + @override String get location => GoRouteData.$location( '/person', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } RouteBase get $hotdogRouteWithEverything => GoRouteData.$route( path: '/:ketchup', - factory: $HotdogRouteWithEverythingExtension._fromState, + factory: _$HotdogRouteWithEverything._fromState, ); -extension $HotdogRouteWithEverythingExtension on HotdogRouteWithEverything { +mixin _$HotdogRouteWithEverything on GoRouteData { static HotdogRouteWithEverything _fromState(GoRouterState state) => HotdogRouteWithEverything( _$boolConverter(state.pathParameters['ketchup']!)!, @@ -159,23 +192,30 @@ extension $HotdogRouteWithEverythingExtension on HotdogRouteWithEverything { state.extra as Sauce, ); + HotdogRouteWithEverything get _self => this as HotdogRouteWithEverything; + + @override String get location => GoRouteData.$location( - '/${Uri.encodeComponent(ketchup.toString())}', + '/${Uri.encodeComponent(_self.ketchup.toString())}', queryParams: { - if (mustard != null) 'mustard': mustard, + if (_self.mustard != null) 'mustard': _self.mustard, }, ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } bool _$boolConverter(String value) { diff --git a/packages/go_router_builder/example/lib/shell_route_example.dart b/packages/go_router_builder/example/lib/shell_route_example.dart index 93e1df207a8..fbbc078cf81 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.dart @@ -53,7 +53,7 @@ class MyShellRouteData extends ShellRouteData { } } -class FooRouteData extends GoRouteData { +class FooRouteData extends GoRouteData with _$FooRouteData { const FooRouteData(); @override @@ -62,7 +62,7 @@ class FooRouteData extends GoRouteData { } } -class BarRouteData extends GoRouteData { +class BarRouteData extends GoRouteData with _$BarRouteData { const BarRouteData(); @override @@ -133,7 +133,7 @@ class BarScreen extends StatelessWidget { } @TypedGoRoute(path: '/login') -class LoginRoute extends GoRouteData { +class LoginRoute extends GoRouteData with _$LoginRoute { const LoginRoute(); @override diff --git a/packages/go_router_builder/example/lib/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart index bc655746b61..0b2e61ebee3 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -18,11 +18,11 @@ RouteBase get $myShellRouteData => ShellRouteData.$route( routes: [ GoRouteData.$route( path: '/foo', - factory: $FooRouteDataExtension._fromState, + factory: _$FooRouteData._fromState, ), GoRouteData.$route( path: '/bar', - factory: $BarRouteDataExtension._fromState, + factory: _$BarRouteData._fromState, ), ], ); @@ -32,58 +32,73 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $FooRouteDataExtension on FooRouteData { +mixin _$FooRouteData on GoRouteData { static FooRouteData _fromState(GoRouterState state) => const FooRouteData(); + @override String get location => GoRouteData.$location( '/foo', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $BarRouteDataExtension on BarRouteData { +mixin _$BarRouteData on GoRouteData { static BarRouteData _fromState(GoRouterState state) => const BarRouteData(); + @override String get location => GoRouteData.$location( '/bar', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $loginRoute => GoRouteData.$route( path: '/login', - factory: $LoginRouteExtension._fromState, + factory: _$LoginRoute._fromState, ); -extension $LoginRouteExtension on LoginRoute { +mixin _$LoginRoute on GoRouteData { static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); + @override String get location => GoRouteData.$location( '/login', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart index 5448f7e1918..415a8f0b31b 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart @@ -100,7 +100,7 @@ class MyShellRouteScreen extends StatelessWidget { } } -class HomeRouteData extends GoRouteData { +class HomeRouteData extends GoRouteData with _$HomeRouteData { const HomeRouteData(); @override @@ -109,7 +109,7 @@ class HomeRouteData extends GoRouteData { } } -class UsersRouteData extends GoRouteData { +class UsersRouteData extends GoRouteData with _$UsersRouteData { const UsersRouteData(); @override @@ -143,7 +143,7 @@ class DialogPage extends Page { } } -class UserRouteData extends GoRouteData { +class UserRouteData extends GoRouteData with _$UserRouteData { const UserRouteData({required this.id}); // Without this static key, the dialog will not cover the navigation rail. diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart index 4a4cb4ff9a2..759588b2d28 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart @@ -18,16 +18,16 @@ RouteBase get $myShellRouteData => ShellRouteData.$route( routes: [ GoRouteData.$route( path: '/home', - factory: $HomeRouteDataExtension._fromState, + factory: _$HomeRouteData._fromState, ), GoRouteData.$route( path: '/users', - factory: $UsersRouteDataExtension._fromState, + factory: _$UsersRouteData._fromState, routes: [ GoRouteData.$route( path: ':id', parentNavigatorKey: UserRouteData.$parentNavigatorKey, - factory: $UserRouteDataExtension._fromState, + factory: _$UserRouteData._fromState, ), ], ), @@ -39,56 +39,73 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $HomeRouteDataExtension on HomeRouteData { +mixin _$HomeRouteData on GoRouteData { static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData(); + @override String get location => GoRouteData.$location( '/home', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $UsersRouteDataExtension on UsersRouteData { +mixin _$UsersRouteData on GoRouteData { static UsersRouteData _fromState(GoRouterState state) => const UsersRouteData(); + @override String get location => GoRouteData.$location( '/users', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $UserRouteDataExtension on UserRouteData { +mixin _$UserRouteData on GoRouteData { static UserRouteData _fromState(GoRouterState state) => UserRouteData( id: int.parse(state.pathParameters['id']!)!, ); + UserRouteData get _self => this as UserRouteData; + + @override String get location => GoRouteData.$location( - '/users/${Uri.encodeComponent(id.toString())}', + '/users/${Uri.encodeComponent(_self.id.toString())}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart b/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart index a69e05e749e..326068ba8bc 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart @@ -103,7 +103,7 @@ class MyShellRouteScreen extends StatelessWidget { } } -class HomeRouteData extends GoRouteData { +class HomeRouteData extends GoRouteData with _$HomeRouteData { const HomeRouteData(); @override @@ -112,7 +112,7 @@ class HomeRouteData extends GoRouteData { } } -class UsersRouteData extends GoRouteData { +class UsersRouteData extends GoRouteData with _$UsersRouteData { const UsersRouteData(); @override @@ -146,7 +146,7 @@ class DialogPage extends Page { } } -class UserRouteData extends GoRouteData { +class UserRouteData extends GoRouteData with _$UserRouteData { const UserRouteData({required this.id}); // Without this static key, the dialog will not cover the navigation rail. diff --git a/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart index 52d3c58f1cb..e510a5f3a80 100644 --- a/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart @@ -18,15 +18,15 @@ RouteBase get $myShellRouteData => ShellRouteData.$route( routes: [ GoRouteData.$route( path: '/home', - factory: $HomeRouteDataExtension._fromState, + factory: _$HomeRouteData._fromState, ), GoRouteData.$route( path: '/users', - factory: $UsersRouteDataExtension._fromState, + factory: _$UsersRouteData._fromState, routes: [ GoRouteData.$route( path: ':id', - factory: $UserRouteDataExtension._fromState, + factory: _$UserRouteData._fromState, ), ], ), @@ -38,56 +38,73 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $HomeRouteDataExtension on HomeRouteData { +mixin _$HomeRouteData on GoRouteData { static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData(); + @override String get location => GoRouteData.$location( '/home', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $UsersRouteDataExtension on UsersRouteData { +mixin _$UsersRouteData on GoRouteData { static UsersRouteData _fromState(GoRouterState state) => const UsersRouteData(); + @override String get location => GoRouteData.$location( '/users', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $UserRouteDataExtension on UserRouteData { +mixin _$UserRouteData on GoRouteData { static UserRouteData _fromState(GoRouterState state) => UserRouteData( id: int.parse(state.pathParameters['id']!)!, ); + UserRouteData get _self => this as UserRouteData; + + @override String get location => GoRouteData.$location( - '/users/${Uri.encodeComponent(id.toString())}', + '/users/${Uri.encodeComponent(_self.id.toString())}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/simple_example.dart b/packages/go_router_builder/example/lib/simple_example.dart index bfef272b186..a2e946c49e0 100644 --- a/packages/go_router_builder/example/lib/simple_example.dart +++ b/packages/go_router_builder/example/lib/simple_example.dart @@ -32,14 +32,14 @@ class App extends StatelessWidget { TypedGoRoute(path: 'family/:familyId') ], ) -class HomeRoute extends GoRouteData { +class HomeRoute extends GoRouteData with _$HomeRoute { const HomeRoute(); @override Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); } -class FamilyRoute extends GoRouteData { +class FamilyRoute extends GoRouteData with _$FamilyRoute { const FamilyRoute(this.familyId); final String familyId; diff --git a/packages/go_router_builder/example/lib/simple_example.g.dart b/packages/go_router_builder/example/lib/simple_example.g.dart index f87f0ac730c..4ecd74e3cbf 100644 --- a/packages/go_router_builder/example/lib/simple_example.g.dart +++ b/packages/go_router_builder/example/lib/simple_example.g.dart @@ -15,47 +15,59 @@ List get $appRoutes => [ RouteBase get $homeRoute => GoRouteData.$route( path: '/', name: 'Home', - factory: $HomeRouteExtension._fromState, + factory: _$HomeRoute._fromState, routes: [ GoRouteData.$route( path: 'family/:familyId', - factory: $FamilyRouteExtension._fromState, + factory: _$FamilyRoute._fromState, ), ], ); -extension $HomeRouteExtension on HomeRoute { +mixin _$HomeRoute on GoRouteData { static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + @override String get location => GoRouteData.$location( '/', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $FamilyRouteExtension on FamilyRoute { +mixin _$FamilyRoute on GoRouteData { static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( state.pathParameters['familyId']!, ); + FamilyRoute get _self => this as FamilyRoute; + + @override String get location => GoRouteData.$location( - '/family/${Uri.encodeComponent(familyId)}', + '/family/${Uri.encodeComponent(_self.familyId)}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart index ad5ba8c82fd..6889403bc3a 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.dart @@ -86,7 +86,7 @@ class BranchBData extends StatefulShellBranchData { static const String $restorationScopeId = 'restorationScopeId'; } -class DetailsARouteData extends GoRouteData { +class DetailsARouteData extends GoRouteData with _$DetailsARouteData { const DetailsARouteData(); @override @@ -95,7 +95,7 @@ class DetailsARouteData extends GoRouteData { } } -class DetailsBRouteData extends GoRouteData { +class DetailsBRouteData extends GoRouteData with _$DetailsBRouteData { const DetailsBRouteData(); @override diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart index 9be15d9859a..d0eaa8ddf6a 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart @@ -21,7 +21,7 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( routes: [ GoRouteData.$route( path: '/detailsA', - factory: $DetailsARouteDataExtension._fromState, + factory: _$DetailsARouteData._fromState, ), ], ), @@ -31,7 +31,7 @@ RouteBase get $myShellRouteData => StatefulShellRouteData.$route( routes: [ GoRouteData.$route( path: '/detailsB', - factory: $DetailsBRouteDataExtension._fromState, + factory: _$DetailsBRouteData._fromState, ), ], ), @@ -43,38 +43,48 @@ extension $MyShellRouteDataExtension on MyShellRouteData { const MyShellRouteData(); } -extension $DetailsARouteDataExtension on DetailsARouteData { +mixin _$DetailsARouteData on GoRouteData { static DetailsARouteData _fromState(GoRouterState state) => const DetailsARouteData(); + @override String get location => GoRouteData.$location( '/detailsA', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $DetailsBRouteDataExtension on DetailsBRouteData { +mixin _$DetailsBRouteData on GoRouteData { static DetailsBRouteData _fromState(GoRouterState state) => const DetailsBRouteData(); + @override String get location => GoRouteData.$location( '/detailsB', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.dart b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.dart index a944e7d0997..0e05a36e9ad 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.dart @@ -88,7 +88,7 @@ class OrdersShellBranchData extends StatefulShellBranchData { const OrdersShellBranchData(); } -class HomeRouteData extends GoRouteData { +class HomeRouteData extends GoRouteData with _$HomeRouteData { const HomeRouteData(); @override @@ -103,7 +103,7 @@ enum NotificationsPageSection { archive, } -class NotificationsRouteData extends GoRouteData { +class NotificationsRouteData extends GoRouteData with _$NotificationsRouteData { const NotificationsRouteData({ required this.section, }); @@ -118,7 +118,7 @@ class NotificationsRouteData extends GoRouteData { } } -class OrdersRouteData extends GoRouteData { +class OrdersRouteData extends GoRouteData with _$OrdersRouteData { const OrdersRouteData(); @override diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart index f86f360772d..c7e63a1fc7f 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart @@ -19,7 +19,7 @@ RouteBase get $mainShellRouteData => StatefulShellRouteData.$route( routes: [ GoRouteData.$route( path: '/home', - factory: $HomeRouteDataExtension._fromState, + factory: _$HomeRouteData._fromState, ), ], ), @@ -28,7 +28,7 @@ RouteBase get $mainShellRouteData => StatefulShellRouteData.$route( routes: [ GoRouteData.$route( path: '/notifications/:section', - factory: $NotificationsRouteDataExtension._fromState, + factory: _$NotificationsRouteData._fromState, ), ], ), @@ -36,7 +36,7 @@ RouteBase get $mainShellRouteData => StatefulShellRouteData.$route( routes: [ GoRouteData.$route( path: '/orders', - factory: $OrdersRouteDataExtension._fromState, + factory: _$OrdersRouteData._fromState, ), ], ), @@ -48,41 +48,53 @@ extension $MainShellRouteDataExtension on MainShellRouteData { const MainShellRouteData(); } -extension $HomeRouteDataExtension on HomeRouteData { +mixin _$HomeRouteData on GoRouteData { static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData(); + @override String get location => GoRouteData.$location( '/home', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } -extension $NotificationsRouteDataExtension on NotificationsRouteData { +mixin _$NotificationsRouteData on GoRouteData { static NotificationsRouteData _fromState(GoRouterState state) => NotificationsRouteData( section: _$NotificationsPageSectionEnumMap ._$fromName(state.pathParameters['section']!)!, ); + NotificationsRouteData get _self => this as NotificationsRouteData; + + @override String get location => GoRouteData.$location( - '/notifications/${Uri.encodeComponent(_$NotificationsPageSectionEnumMap[section]!)}', + '/notifications/${Uri.encodeComponent(_$NotificationsPageSectionEnumMap[_self.section]!)}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } @@ -92,21 +104,26 @@ const _$NotificationsPageSectionEnumMap = { NotificationsPageSection.archive: 'archive', }; -extension $OrdersRouteDataExtension on OrdersRouteData { +mixin _$OrdersRouteData on GoRouteData { static OrdersRouteData _fromState(GoRouterState state) => const OrdersRouteData(); + @override String get location => GoRouteData.$location( '/orders', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 3e4c9175bd8..d95663d15ac 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -4,6 +4,7 @@ import 'dart:collection'; +import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; @@ -237,9 +238,20 @@ class GoRouteConfig extends RouteBaseConfig { // here to ensure it matches Uri.encodeComponent nullability final DartType? type = _field(pathParameter)?.returnType; - final String value = - '\${Uri.encodeComponent(${_encodeFor(pathParameter)}${(type?.isEnum ?? false) ? '!' : (type?.isNullableType ?? false) ? "?? ''" : ''})}'; - return MapEntry(pathParameter, value); + final StringBuffer valueBuffer = StringBuffer(); + + valueBuffer.write(r'${Uri.encodeComponent('); + valueBuffer.write(_encodeFor(pathParameter)); + + if (type?.isEnum ?? false) { + valueBuffer.write('!'); + } else if (type?.isNullableType ?? false) { + valueBuffer.write("?? ''"); + } + + valueBuffer.write(')}'); + + return MapEntry(pathParameter, valueBuffer.toString()); }), ); final String location = patternToPath(_rawJoinedPath, pathParameters); @@ -271,6 +283,16 @@ class GoRouteConfig extends RouteBaseConfig { return buffer.toString(); } + String get _castedSelf { + if (_pathParams.isEmpty && + _ctorQueryParams.isEmpty && + _extraParam == null) { + return ''; + } + + return '\n$_className get $selfFieldName => this as $_className;\n'; + } + String _decodeFor(ParameterElement element) { if (element.isRequired) { if (element.type.nullabilitySuffix == NullabilitySuffix.question && @@ -328,7 +350,7 @@ class GoRouteConfig extends RouteBaseConfig { compareField(param, parameterName, param.defaultValueCode!), ); } else if (param.type.isNullableType) { - conditions.add('$parameterName != null'); + conditions.add('$selfFieldName.$parameterName != null'); } String line = ''; if (conditions.isNotEmpty) { @@ -372,29 +394,49 @@ class GoRouteConfig extends RouteBaseConfig { @override Iterable classDeclarations() => [ - _extensionDefinition, + _mixinDefinition, ..._enumDeclarations(), ]; - String get _extensionDefinition => ''' -extension $_extensionName on $_className { - static $_className _fromState(GoRouterState state) $_fromStateConstructor + String get _mixinDefinition { + final bool hasMixin = getNodeDeclaration(routeDataClass) + ?.withClause + ?.mixinTypes + .any((NamedType e) => e.name2.toString() == _mixinName) ?? + false; - String get location => GoRouteData.\$location($_locationArgs,$_locationQueryParams); + if (!hasMixin) { + throw InvalidGenerationSourceError( + 'Missing mixin clause `with $_mixinName`', + element: routeDataClass, + ); + } + return ''' +mixin $_mixinName on GoRouteData { + static $_className _fromState(GoRouterState state) $_fromStateConstructor + $_castedSelf + @override + String get location => GoRouteData.\$location($_locationArgs,$_locationQueryParams); + + @override void go(BuildContext context) => - context.go(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); - + context.go(location${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override Future push(BuildContext context) => - context.push(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); - + context.push(location${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); - + context.pushReplacement(location${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); + + @override void replace(BuildContext context) => - context.replace(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + context.replace(location${_extraParam != null ? ', extra: $selfFieldName.$extraFieldName' : ''}); } '''; + } /// Returns code representing the constant maps that contain the `enum` to /// [String] mapping for each referenced enum. @@ -420,8 +462,7 @@ extension $_extensionName on $_className { } @override - String get factorConstructorParameters => - 'factory: $_extensionName._fromState,'; + String get factorConstructorParameters => 'factory: $_mixinName._fromState,'; @override String get routeConstructorParameters => ''' @@ -688,6 +729,8 @@ RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; String get _className => routeDataClass.name; + String get _mixinName => '_\$$_className'; + String get _extensionName => '\$${_className}Extension'; String _invokesRouteConstructor() { diff --git a/packages/go_router_builder/lib/src/type_helpers.dart b/packages/go_router_builder/lib/src/type_helpers.dart index ecf7bbd3067..5f809c335d3 100644 --- a/packages/go_router_builder/lib/src/type_helpers.dart +++ b/packages/go_router_builder/lib/src/type_helpers.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. +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/session.dart'; +import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:source_gen/source_gen.dart'; @@ -25,6 +28,9 @@ const String enumExtensionHelperName = r'_$fromName'; /// be passed to a route. const String extraFieldName = r'$extra'; +/// The name of the generated, private getter for casting `this` (the mixin) to the class type. +const String selfFieldName = '_self'; + /// Shared start of error message related to a likely code issue. const String likelyIssueMessage = 'Should never get here! File an issue!'; @@ -79,7 +85,8 @@ String decodeParameter(ParameterElement element, Set pathParameters) { String encodeField(PropertyAccessorElement element) { for (final _TypeHelper helper in _helpers) { if (helper._matchesType(element.returnType)) { - return helper._encode(element.name, element.returnType); + return helper._encode( + '$selfFieldName.${element.name}', element.returnType); } } @@ -89,13 +96,30 @@ String encodeField(PropertyAccessorElement element) { ); } +/// Returns an AstNode type from a InterfaceElement. +T? getNodeDeclaration(InterfaceElement element) { + final AnalysisSession? session = element.session; + if (session == null) { + return null; + } + + final ParsedLibraryResult parsedLibrary = + session.getParsedLibraryByElement(element.library) as ParsedLibraryResult; + final ElementDeclarationResult? declaration = + parsedLibrary.getElementDeclaration(element); + final AstNode? node = declaration?.node; + + return node is T ? node : null; +} + /// Returns the comparison of a parameter with its default value. /// /// Otherwise, throws an [InvalidGenerationSourceError]. String compareField(ParameterElement param, String value1, String value2) { for (final _TypeHelper helper in _helpers) { if (helper._matchesType(param.type)) { - return helper._compare(param.name, param.defaultValueCode!); + return helper._compare( + '$selfFieldName.${param.name}', param.defaultValueCode!); } } diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 567f8f7808b..aeb819b55dc 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 2.9.1 +version: 3.0.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22 diff --git a/packages/go_router_builder/test_inputs/bad_path_pattern.dart b/packages/go_router_builder/test_inputs/bad_path_pattern.dart index b6c6012ecbb..8f2d00a0f81 100644 --- a/packages/go_router_builder/test_inputs/bad_path_pattern.dart +++ b/packages/go_router_builder/test_inputs/bad_path_pattern.dart @@ -4,5 +4,7 @@ import 'package:go_router/go_router.dart'; +mixin _$BadPathParam {} + @TypedGoRoute(path: 'bob/:id') -class BadPathParam extends GoRouteData {} +class BadPathParam extends GoRouteData with _$BadPathParam {} diff --git a/packages/go_router_builder/test_inputs/case_sensitivity.dart b/packages/go_router_builder/test_inputs/case_sensitivity.dart index 44066f2bee9..d14feedbc70 100644 --- a/packages/go_router_builder/test_inputs/case_sensitivity.dart +++ b/packages/go_router_builder/test_inputs/case_sensitivity.dart @@ -4,11 +4,14 @@ import 'package:go_router/go_router.dart'; +mixin _$CaseSensitiveRoute {} +mixin _$NotCaseSensitiveRoute {} + @TypedGoRoute(path: '/case-sensitive-route') -class CaseSensitiveRoute extends GoRouteData {} +class CaseSensitiveRoute extends GoRouteData with _$CaseSensitiveRoute {} @TypedGoRoute( path: '/not-case-sensitive-route', caseSensitive: false, ) -class NotCaseSensitiveRoute extends GoRouteData {} +class NotCaseSensitiveRoute extends GoRouteData with _$NotCaseSensitiveRoute {} diff --git a/packages/go_router_builder/test_inputs/case_sensitivity.dart.expect b/packages/go_router_builder/test_inputs/case_sensitivity.dart.expect index 15f76416286..acbc044d5bf 100644 --- a/packages/go_router_builder/test_inputs/case_sensitivity.dart.expect +++ b/packages/go_router_builder/test_inputs/case_sensitivity.dart.expect @@ -1,46 +1,56 @@ RouteBase get $caseSensitiveRoute => GoRouteData.$route( path: '/case-sensitive-route', - factory: $CaseSensitiveRouteExtension._fromState, + factory: _$CaseSensitiveRoute._fromState, ); -extension $CaseSensitiveRouteExtension on CaseSensitiveRoute { +mixin _$CaseSensitiveRoute on GoRouteData { static CaseSensitiveRoute _fromState(GoRouterState state) => CaseSensitiveRoute(); + @override String get location => GoRouteData.$location( '/case-sensitive-route', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } RouteBase get $notCaseSensitiveRoute => GoRouteData.$route( path: '/not-case-sensitive-route', caseSensitive: false, - factory: $NotCaseSensitiveRouteExtension._fromState, + factory: _$NotCaseSensitiveRoute._fromState, ); -extension $NotCaseSensitiveRouteExtension on NotCaseSensitiveRoute { +mixin _$NotCaseSensitiveRoute on GoRouteData { static NotCaseSensitiveRoute _fromState(GoRouterState state) => NotCaseSensitiveRoute(); + @override String get location => GoRouteData.$location( '/not-case-sensitive-route', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/default_value.dart b/packages/go_router_builder/test_inputs/default_value.dart index 209e7f4f52f..3d04653b7aa 100644 --- a/packages/go_router_builder/test_inputs/default_value.dart +++ b/packages/go_router_builder/test_inputs/default_value.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$DefaultValueRoute {} + @TypedGoRoute(path: '/default-value-route') -class DefaultValueRoute extends GoRouteData { +class DefaultValueRoute extends GoRouteData with _$DefaultValueRoute { DefaultValueRoute({this.param = 0}); final int param; } diff --git a/packages/go_router_builder/test_inputs/default_value.dart.expect b/packages/go_router_builder/test_inputs/default_value.dart.expect index 9d9d5e8ec83..9302c4b2346 100644 --- a/packages/go_router_builder/test_inputs/default_value.dart.expect +++ b/packages/go_router_builder/test_inputs/default_value.dart.expect @@ -1,29 +1,36 @@ RouteBase get $defaultValueRoute => GoRouteData.$route( path: '/default-value-route', - factory: $DefaultValueRouteExtension._fromState, + factory: _$DefaultValueRoute._fromState, ); -extension $DefaultValueRouteExtension on DefaultValueRoute { +mixin _$DefaultValueRoute on GoRouteData { static DefaultValueRoute _fromState(GoRouterState state) => DefaultValueRoute( param: _$convertMapValue('param', state.uri.queryParameters, int.parse) ?? 0, ); + DefaultValueRoute get _self => this as DefaultValueRoute; + + @override String get location => GoRouteData.$location( '/default-value-route', queryParams: { - if (param != 0) 'param': param.toString(), + if (_self.param != 0) 'param': _self.param.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/enum_parameter.dart b/packages/go_router_builder/test_inputs/enum_parameter.dart index 72b41486b88..ce1492338bb 100644 --- a/packages/go_router_builder/test_inputs/enum_parameter.dart +++ b/packages/go_router_builder/test_inputs/enum_parameter.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$EnumParam {} + @TypedGoRoute(path: '/:y') -class EnumParam extends GoRouteData { +class EnumParam extends GoRouteData with _$EnumParam { EnumParam({required this.y}); final EnumTest y; } diff --git a/packages/go_router_builder/test_inputs/enum_parameter.dart.expect b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect index 42d27809f85..341e1bab282 100644 --- a/packages/go_router_builder/test_inputs/enum_parameter.dart.expect +++ b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect @@ -1,24 +1,31 @@ RouteBase get $enumParam => GoRouteData.$route( path: '/:y', - factory: $EnumParamExtension._fromState, + factory: _$EnumParam._fromState, ); -extension $EnumParamExtension on EnumParam { +mixin _$EnumParam on GoRouteData { static EnumParam _fromState(GoRouterState state) => EnumParam( y: _$EnumTestEnumMap._$fromName(state.pathParameters['y']!)!, ); + EnumParam get _self => this as EnumParam; + + @override String get location => GoRouteData.$location( - '/${Uri.encodeComponent(_$EnumTestEnumMap[y]!)}', + '/${Uri.encodeComponent(_$EnumTestEnumMap[_self.y]!)}', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/extra_value.dart b/packages/go_router_builder/test_inputs/extra_value.dart index 60e80917212..3951bea5329 100644 --- a/packages/go_router_builder/test_inputs/extra_value.dart +++ b/packages/go_router_builder/test_inputs/extra_value.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$ExtraValueRoute {} + @TypedGoRoute(path: '/default-value-route') -class ExtraValueRoute extends GoRouteData { +class ExtraValueRoute extends GoRouteData with _$ExtraValueRoute { ExtraValueRoute({this.param = 0, this.$extra}); final int param; final int? $extra; diff --git a/packages/go_router_builder/test_inputs/extra_value.dart.expect b/packages/go_router_builder/test_inputs/extra_value.dart.expect index fccced1ce01..944b8a4b25c 100644 --- a/packages/go_router_builder/test_inputs/extra_value.dart.expect +++ b/packages/go_router_builder/test_inputs/extra_value.dart.expect @@ -1,9 +1,9 @@ RouteBase get $extraValueRoute => GoRouteData.$route( path: '/default-value-route', - factory: $ExtraValueRouteExtension._fromState, + factory: _$ExtraValueRoute._fromState, ); -extension $ExtraValueRouteExtension on ExtraValueRoute { +mixin _$ExtraValueRoute on GoRouteData { static ExtraValueRoute _fromState(GoRouterState state) => ExtraValueRoute( param: _$convertMapValue('param', state.uri.queryParameters, int.parse) ?? @@ -11,23 +11,30 @@ extension $ExtraValueRouteExtension on ExtraValueRoute { $extra: state.extra as int?, ); + ExtraValueRoute get _self => this as ExtraValueRoute; + + @override String get location => GoRouteData.$location( '/default-value-route', queryParams: { - if (param != 0) 'param': param.toString(), + if (_self.param != 0) 'param': _self.param.toString(), }, ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } T? _$convertMapValue( diff --git a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart index 3abd7a92318..01b09784d25 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart +++ b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$IterableDefaultValueRoute {} + @TypedGoRoute(path: '/iterable-default-value-route') -class IterableDefaultValueRoute extends GoRouteData { +class IterableDefaultValueRoute extends GoRouteData + with _$IterableDefaultValueRoute { IterableDefaultValueRoute({this.param = const [0]}); final Iterable param; } diff --git a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect index 683a64ec7ca..35e4cb75fb5 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect +++ b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect @@ -1,9 +1,9 @@ RouteBase get $iterableDefaultValueRoute => GoRouteData.$route( path: '/iterable-default-value-route', - factory: $IterableDefaultValueRouteExtension._fromState, + factory: _$IterableDefaultValueRoute._fromState, ); -extension $IterableDefaultValueRouteExtension on IterableDefaultValueRoute { +mixin _$IterableDefaultValueRoute on GoRouteData { static IterableDefaultValueRoute _fromState(GoRouterState state) => IterableDefaultValueRoute( param: (state.uri.queryParametersAll['param'] @@ -12,21 +12,28 @@ extension $IterableDefaultValueRouteExtension on IterableDefaultValueRoute { const [0], ); + IterableDefaultValueRoute get _self => this as IterableDefaultValueRoute; + + @override String get location => GoRouteData.$location( '/iterable-default-value-route', queryParams: { - if (!_$iterablesEqual(param, const [0])) - 'param': param.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.param, const [0])) + 'param': _self.param.map((e) => e.toString()).toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart b/packages/go_router_builder/test_inputs/iterable_with_enum.dart index afdb7689c7b..0c0543ca289 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_enum.dart +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$IterableWithEnumRoute {} + @TypedGoRoute(path: '/iterable-with-enum') -class IterableWithEnumRoute extends GoRouteData { +class IterableWithEnumRoute extends GoRouteData with _$IterableWithEnumRoute { IterableWithEnumRoute({this.param}); final Iterable? param; diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect index 88a9f97dc27..ef7fd9fec7c 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect @@ -1,9 +1,9 @@ RouteBase get $iterableWithEnumRoute => GoRouteData.$route( path: '/iterable-with-enum', - factory: $IterableWithEnumRouteExtension._fromState, + factory: _$IterableWithEnumRoute._fromState, ); -extension $IterableWithEnumRouteExtension on IterableWithEnumRoute { +mixin _$IterableWithEnumRoute on GoRouteData { static IterableWithEnumRoute _fromState(GoRouterState state) => IterableWithEnumRoute( param: (state.uri.queryParametersAll['param'] @@ -12,22 +12,30 @@ extension $IterableWithEnumRouteExtension on IterableWithEnumRoute { as Iterable?), ); + IterableWithEnumRoute get _self => this as IterableWithEnumRoute; + + @override String get location => GoRouteData.$location( '/iterable-with-enum', queryParams: { - if (param != null) - 'param': - param?.map((e) => _$EnumOnlyUsedInIterableEnumMap[e]).toList(), + if (_self.param != null) + 'param': _self.param + ?.map((e) => _$EnumOnlyUsedInIterableEnumMap[e]) + .toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/list.dart b/packages/go_router_builder/test_inputs/list.dart index e7b3e3d975d..07762f57bfb 100644 --- a/packages/go_router_builder/test_inputs/list.dart +++ b/packages/go_router_builder/test_inputs/list.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$ListRoute {} + @TypedGoRoute(path: '/list-route') -class ListRoute extends GoRouteData { +class ListRoute extends GoRouteData with _$ListRoute { ListRoute({ required this.ids, this.nullableIds, diff --git a/packages/go_router_builder/test_inputs/list.dart.expect b/packages/go_router_builder/test_inputs/list.dart.expect index bd756314184..abe23c4c4f1 100644 --- a/packages/go_router_builder/test_inputs/list.dart.expect +++ b/packages/go_router_builder/test_inputs/list.dart.expect @@ -1,9 +1,9 @@ RouteBase get $listRoute => GoRouteData.$route( path: '/list-route', - factory: $ListRouteExtension._fromState, + factory: _$ListRoute._fromState, ); -extension $ListRouteExtension on ListRoute { +mixin _$ListRoute on GoRouteData { static ListRoute _fromState(GoRouterState state) => ListRoute( ids: (state.uri.queryParametersAll['ids'] ?.map(int.parse) @@ -25,25 +25,33 @@ extension $ListRouteExtension on ListRoute { const [0], ); + ListRoute get _self => this as ListRoute; + + @override String get location => GoRouteData.$location( '/list-route', queryParams: { - 'ids': ids.map((e) => e.toString()).toList(), - if (nullableIds != null) - 'nullable-ids': nullableIds?.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(idsWithDefaultValue, const [0])) + 'ids': _self.ids.map((e) => e.toString()).toList(), + if (_self.nullableIds != null) + 'nullable-ids': + _self.nullableIds?.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.idsWithDefaultValue, const [0])) 'ids-with-default-value': - idsWithDefaultValue.map((e) => e.toString()).toList(), + _self.idsWithDefaultValue.map((e) => e.toString()).toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/missing_type_annotation.dart b/packages/go_router_builder/test_inputs/missing_type_annotation.dart index 2b3ff58dda5..93e65ea6b88 100644 --- a/packages/go_router_builder/test_inputs/missing_type_annotation.dart +++ b/packages/go_router_builder/test_inputs/missing_type_annotation.dart @@ -4,5 +4,7 @@ import 'package:go_router/go_router.dart'; +mixin _$MissingTypeAnnotation {} + @TypedGoRoute(path: 'bob') -class MissingTypeAnnotation extends GoRouteData {} +class MissingTypeAnnotation extends GoRouteData with _$MissingTypeAnnotation {} diff --git a/packages/go_router_builder/test_inputs/named_escaped_route.dart b/packages/go_router_builder/test_inputs/named_escaped_route.dart index eb5e26bc563..6222aa74873 100644 --- a/packages/go_router_builder/test_inputs/named_escaped_route.dart +++ b/packages/go_router_builder/test_inputs/named_escaped_route.dart @@ -4,5 +4,7 @@ import 'package:go_router/go_router.dart'; +mixin _$NamedEscapedRoute {} + @TypedGoRoute(path: '/named-route', name: r'named$Route') -class NamedEscapedRoute extends GoRouteData {} +class NamedEscapedRoute extends GoRouteData with _$NamedEscapedRoute {} diff --git a/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect b/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect index e4d75ec8163..df9ab5f52d5 100644 --- a/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect +++ b/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect @@ -1,23 +1,28 @@ RouteBase get $namedEscapedRoute => GoRouteData.$route( path: '/named-route', name: r'named$Route', - factory: $NamedEscapedRouteExtension._fromState, + factory: _$NamedEscapedRoute._fromState, ); -extension $NamedEscapedRouteExtension on NamedEscapedRoute { +mixin _$NamedEscapedRoute on GoRouteData { static NamedEscapedRoute _fromState(GoRouterState state) => NamedEscapedRoute(); + @override String get location => GoRouteData.$location( '/named-route', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/named_route.dart b/packages/go_router_builder/test_inputs/named_route.dart index 3e45f7e476f..449b9537c6d 100644 --- a/packages/go_router_builder/test_inputs/named_route.dart +++ b/packages/go_router_builder/test_inputs/named_route.dart @@ -4,5 +4,7 @@ import 'package:go_router/go_router.dart'; +mixin _$NamedRoute {} + @TypedGoRoute(path: '/named-route', name: 'namedRoute') -class NamedRoute extends GoRouteData {} +class NamedRoute extends GoRouteData with _$NamedRoute {} diff --git a/packages/go_router_builder/test_inputs/named_route.dart.expect b/packages/go_router_builder/test_inputs/named_route.dart.expect index 9d4b705530c..b3cf1c42c42 100644 --- a/packages/go_router_builder/test_inputs/named_route.dart.expect +++ b/packages/go_router_builder/test_inputs/named_route.dart.expect @@ -1,22 +1,27 @@ RouteBase get $namedRoute => GoRouteData.$route( path: '/named-route', name: 'namedRoute', - factory: $NamedRouteExtension._fromState, + factory: _$NamedRoute._fromState, ); -extension $NamedRouteExtension on NamedRoute { +mixin _$NamedRoute on GoRouteData { static NamedRoute _fromState(GoRouterState state) => NamedRoute(); + @override String get location => GoRouteData.$location( '/named-route', ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/no_mixin.dart b/packages/go_router_builder/test_inputs/no_mixin.dart new file mode 100644 index 00000000000..b894313adb1 --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin.dart @@ -0,0 +1,8 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:go_router/go_router.dart'; + +@TypedGoRoute(path: '/') +class HomeRoute extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/no_mixin.dart.expect b/packages/go_router_builder/test_inputs/no_mixin.dart.expect new file mode 100644 index 00000000000..dc98f351c1f --- /dev/null +++ b/packages/go_router_builder/test_inputs/no_mixin.dart.expect @@ -0,0 +1 @@ +Missing mixin clause `with _$HomeRoute` diff --git a/packages/go_router_builder/test_inputs/nullable_default_value.dart b/packages/go_router_builder/test_inputs/nullable_default_value.dart index dcf2ea8e9f1..b41f5733e01 100644 --- a/packages/go_router_builder/test_inputs/nullable_default_value.dart +++ b/packages/go_router_builder/test_inputs/nullable_default_value.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$NullableDefaultValueRoute {} + @TypedGoRoute(path: '/nullable-default-value-route') -class NullableDefaultValueRoute extends GoRouteData { +class NullableDefaultValueRoute extends GoRouteData + with _$NullableDefaultValueRoute { NullableDefaultValueRoute({this.param = 0}); final int? param; } diff --git a/packages/go_router_builder/test_inputs/required_extra_value.dart b/packages/go_router_builder/test_inputs/required_extra_value.dart index 98d6e7a9469..7c244b5b85c 100644 --- a/packages/go_router_builder/test_inputs/required_extra_value.dart +++ b/packages/go_router_builder/test_inputs/required_extra_value.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$RequiredExtraValueRoute {} + @TypedGoRoute(path: '/default-value-route') -class RequiredExtraValueRoute extends GoRouteData { +class RequiredExtraValueRoute extends GoRouteData + with _$RequiredExtraValueRoute { RequiredExtraValueRoute({required this.$extra}); final int $extra; } diff --git a/packages/go_router_builder/test_inputs/required_extra_value.dart.expect b/packages/go_router_builder/test_inputs/required_extra_value.dart.expect index 108cb124458..662c55b6758 100644 --- a/packages/go_router_builder/test_inputs/required_extra_value.dart.expect +++ b/packages/go_router_builder/test_inputs/required_extra_value.dart.expect @@ -1,26 +1,33 @@ RouteBase get $requiredExtraValueRoute => GoRouteData.$route( path: '/default-value-route', - factory: $RequiredExtraValueRouteExtension._fromState, + factory: _$RequiredExtraValueRoute._fromState, ); -extension $RequiredExtraValueRouteExtension on RequiredExtraValueRoute { +mixin _$RequiredExtraValueRoute on GoRouteData { static RequiredExtraValueRoute _fromState(GoRouterState state) => RequiredExtraValueRoute( $extra: state.extra as int, ); + RequiredExtraValueRoute get _self => this as RequiredExtraValueRoute; + + @override String get location => GoRouteData.$location( '/default-value-route', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } diff --git a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart index 2d7f8a17ac0..88b1deeba68 100644 --- a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart +++ b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart @@ -4,9 +4,12 @@ import 'package:go_router/go_router.dart'; +mixin _$RequiredNullableTypeArgumentsExtraValueRoute {} + @TypedGoRoute( path: '/default-value-route') -class RequiredNullableTypeArgumentsExtraValueRoute extends GoRouteData { +class RequiredNullableTypeArgumentsExtraValueRoute extends GoRouteData + with _$RequiredNullableTypeArgumentsExtraValueRoute { RequiredNullableTypeArgumentsExtraValueRoute({required this.$extra}); final List $extra; } diff --git a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect index 9877d353f4d..2a181ac8576 100644 --- a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect +++ b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart.expect @@ -1,30 +1,36 @@ RouteBase get $requiredNullableTypeArgumentsExtraValueRoute => GoRouteData.$route( path: '/default-value-route', - factory: - $RequiredNullableTypeArgumentsExtraValueRouteExtension._fromState, + factory: _$RequiredNullableTypeArgumentsExtraValueRoute._fromState, ); -extension $RequiredNullableTypeArgumentsExtraValueRouteExtension - on RequiredNullableTypeArgumentsExtraValueRoute { +mixin _$RequiredNullableTypeArgumentsExtraValueRoute on GoRouteData { static RequiredNullableTypeArgumentsExtraValueRoute _fromState( GoRouterState state) => RequiredNullableTypeArgumentsExtraValueRoute( $extra: state.extra as List, ); + RequiredNullableTypeArgumentsExtraValueRoute get _self => + this as RequiredNullableTypeArgumentsExtraValueRoute; + + @override String get location => GoRouteData.$location( '/default-value-route', ); - void go(BuildContext context) => context.go(location, extra: $extra); + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + @override Future push(BuildContext context) => - context.push(location, extra: $extra); + context.push(location, extra: _self.$extra); + @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location, extra: _self.$extra); + @override void replace(BuildContext context) => - context.replace(location, extra: $extra); + context.replace(location, extra: _self.$extra); } diff --git a/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart index 17cadfb6453..33fa3b7a1d1 100644 --- a/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart +++ b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$NullableRequiredParamInPath {} + @TypedGoRoute(path: 'bob/:id') -class NullableRequiredParamInPath extends GoRouteData { +class NullableRequiredParamInPath extends GoRouteData + with _$NullableRequiredParamInPath { NullableRequiredParamInPath({required this.id}); final int? id; } diff --git a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart index 60c0c58cc1e..3b5eecc48c0 100644 --- a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart +++ b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$NullableRequiredParamNotInPath {} + @TypedGoRoute(path: 'bob') -class NullableRequiredParamNotInPath extends GoRouteData { +class NullableRequiredParamNotInPath extends GoRouteData + with _$NullableRequiredParamNotInPath { NullableRequiredParamNotInPath({required this.id}); final int? id; } diff --git a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect index 26ab06e90c2..60c1c0f6a25 100644 --- a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect +++ b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect @@ -1,29 +1,36 @@ RouteBase get $nullableRequiredParamNotInPath => GoRouteData.$route( path: 'bob', - factory: $NullableRequiredParamNotInPathExtension._fromState, + factory: _$NullableRequiredParamNotInPath._fromState, ); -extension $NullableRequiredParamNotInPathExtension - on NullableRequiredParamNotInPath { +mixin _$NullableRequiredParamNotInPath on GoRouteData { static NullableRequiredParamNotInPath _fromState(GoRouterState state) => NullableRequiredParamNotInPath( id: _$convertMapValue('id', state.uri.queryParameters, int.tryParse), ); + NullableRequiredParamNotInPath get _self => + this as NullableRequiredParamNotInPath; + + @override String get location => GoRouteData.$location( 'bob', queryParams: { - if (id != null) 'id': id!.toString(), + if (_self.id != null) 'id': _self.id!.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/required_query_parameter.dart b/packages/go_router_builder/test_inputs/required_query_parameter.dart index d5b5a814136..1dd4f0c2781 100644 --- a/packages/go_router_builder/test_inputs/required_query_parameter.dart +++ b/packages/go_router_builder/test_inputs/required_query_parameter.dart @@ -4,8 +4,11 @@ import 'package:go_router/go_router.dart'; +mixin _$NonNullableRequiredParamNotInPath {} + @TypedGoRoute(path: 'bob') -class NonNullableRequiredParamNotInPath extends GoRouteData { +class NonNullableRequiredParamNotInPath extends GoRouteData + with _$NonNullableRequiredParamNotInPath { NonNullableRequiredParamNotInPath({required this.id}); final int id; } diff --git a/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect b/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect index aac229ed118..23487d96d6e 100644 --- a/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect +++ b/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect @@ -1,28 +1,35 @@ RouteBase get $nonNullableRequiredParamNotInPath => GoRouteData.$route( path: 'bob', - factory: $NonNullableRequiredParamNotInPathExtension._fromState, + factory: _$NonNullableRequiredParamNotInPath._fromState, ); -extension $NonNullableRequiredParamNotInPathExtension - on NonNullableRequiredParamNotInPath { +mixin _$NonNullableRequiredParamNotInPath on GoRouteData { static NonNullableRequiredParamNotInPath _fromState(GoRouterState state) => NonNullableRequiredParamNotInPath( id: int.parse(state.uri.queryParameters['id']!)!, ); + NonNullableRequiredParamNotInPath get _self => + this as NonNullableRequiredParamNotInPath; + + @override String get location => GoRouteData.$location( 'bob', queryParams: { - 'id': id.toString(), + 'id': _self.id.toString(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/set.dart b/packages/go_router_builder/test_inputs/set.dart index 286fd6b236f..6fb05b47eea 100644 --- a/packages/go_router_builder/test_inputs/set.dart +++ b/packages/go_router_builder/test_inputs/set.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$SetRoute {} + @TypedGoRoute(path: '/set-route') -class SetRoute extends GoRouteData { +class SetRoute extends GoRouteData with _$SetRoute { SetRoute({ required this.ids, this.nullableIds, diff --git a/packages/go_router_builder/test_inputs/set.dart.expect b/packages/go_router_builder/test_inputs/set.dart.expect index a8f577bdb00..69d25393d59 100644 --- a/packages/go_router_builder/test_inputs/set.dart.expect +++ b/packages/go_router_builder/test_inputs/set.dart.expect @@ -1,9 +1,9 @@ RouteBase get $setRoute => GoRouteData.$route( path: '/set-route', - factory: $SetRouteExtension._fromState, + factory: _$SetRoute._fromState, ); -extension $SetRouteExtension on SetRoute { +mixin _$SetRoute on GoRouteData { static SetRoute _fromState(GoRouterState state) => SetRoute( ids: (state.uri.queryParametersAll['ids'] ?.map(int.parse) @@ -25,25 +25,33 @@ extension $SetRouteExtension on SetRoute { const {0}, ); + SetRoute get _self => this as SetRoute; + + @override String get location => GoRouteData.$location( '/set-route', queryParams: { - 'ids': ids.map((e) => e.toString()).toList(), - if (nullableIds != null) - 'nullable-ids': nullableIds?.map((e) => e.toString()).toList(), - if (!_$iterablesEqual(idsWithDefaultValue, const {0})) + 'ids': _self.ids.map((e) => e.toString()).toList(), + if (_self.nullableIds != null) + 'nullable-ids': + _self.nullableIds?.map((e) => e.toString()).toList(), + if (!_$iterablesEqual(_self.idsWithDefaultValue, const {0})) 'ids-with-default-value': - idsWithDefaultValue.map((e) => e.toString()).toList(), + _self.idsWithDefaultValue.map((e) => e.toString()).toList(), }, ); + @override void go(BuildContext context) => context.go(location); + @override Future push(BuildContext context) => context.push(location); + @override void pushReplacement(BuildContext context) => context.pushReplacement(location); + @override void replace(BuildContext context) => context.replace(location); } diff --git a/packages/go_router_builder/test_inputs/unsupported_type.dart b/packages/go_router_builder/test_inputs/unsupported_type.dart index 55df2a674d1..7681c321233 100644 --- a/packages/go_router_builder/test_inputs/unsupported_type.dart +++ b/packages/go_router_builder/test_inputs/unsupported_type.dart @@ -4,8 +4,10 @@ import 'package:go_router/go_router.dart'; +mixin _$UnsupportedType {} + @TypedGoRoute(path: 'bob/:id') -class UnsupportedType extends GoRouteData { +class UnsupportedType extends GoRouteData with _$UnsupportedType { UnsupportedType({required this.id}); final Stopwatch id; } 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 d2e113d2f1a..44c04424e50 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,5 +1,6 @@ -## NEXT +## 2.12.0 +* Deprecates `zIndex` parameter in `Marker` in favor of `zIndexInt`. * Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. ## 2.11.1 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart index 0921d2d4ec5..fa64d2d977c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart @@ -135,7 +135,7 @@ class Marker implements MapsObject { /// * is positioned at 0, 0; [position] is `LatLng(0.0, 0.0)` /// * has an axis-aligned icon; [rotation] is 0.0 /// * is visible; [visible] is true - /// * is placed at the base of the drawing order; [zIndex] is 0.0 + /// * is placed at the base of the drawing order; [zIndexInt] is 0 /// * reports [onTap] events /// * reports [onDragEnd] events const Marker({ @@ -150,13 +150,21 @@ class Marker implements MapsObject { this.position = const LatLng(0.0, 0.0), this.rotation = 0.0, this.visible = true, - this.zIndex = 0.0, + @Deprecated( + 'Use zIndexInt instead. ' + 'On some platforms zIndex is truncated to an int, which can lead to incorrect/unstable ordering.', + ) + double zIndex = 0.0, + int zIndexInt = 0, this.clusterManagerId, this.onTap, this.onDrag, this.onDragStart, this.onDragEnd, - }) : assert(0.0 <= alpha && alpha <= 1.0); + }) : assert(0.0 <= alpha && alpha <= 1.0), + assert(zIndex == 0.0 || zIndexInt == 0, + 'Only one of zIndex and zIndexInt can be provided'), + _zIndexNum = zIndexInt == 0 ? zIndex : zIndexInt; /// Uniquely identifies a [Marker]. final MarkerId markerId; @@ -214,12 +222,26 @@ class Marker implements MapsObject { /// True if the marker is visible. final bool visible; + final num _zIndexNum; + + /// The z-index of the marker, used to determine relative drawing order of + /// map overlays. + /// + /// Overlays are drawn in order of z-index, so that lower values means drawn + /// earlier, and thus appearing to be closer to the surface of the Earth. + // TODO(stuartmorgan): Make this an int when removing the deprecated double zIndex parameter. + @Deprecated( + 'Use zIndexInt instead. ' + 'On some platforms zIndex is truncated to an int, which can lead to incorrect/unstable ordering.', + ) + double get zIndex => _zIndexNum.toDouble(); + /// The z-index of the marker, used to determine relative drawing order of /// map overlays. /// /// Overlays are drawn in order of z-index, so that lower values means drawn /// earlier, and thus appearing to be closer to the surface of the Earth. - final double zIndex; + int get zIndexInt => _zIndexNum.round(); /// Callbacks to receive tap events for markers placed on this map. final VoidCallback? onTap; @@ -246,7 +268,12 @@ class Marker implements MapsObject { LatLng? positionParam, double? rotationParam, bool? visibleParam, + @Deprecated( + 'Use zIndexIntParam instead. ' + 'On some platforms zIndex is truncated to an int, which can lead to incorrect/unstable ordering.', + ) double? zIndexParam, + int? zIndexIntParam, VoidCallback? onTapParam, ValueChanged? onDragStartParam, ValueChanged? onDragParam, @@ -266,6 +293,7 @@ class Marker implements MapsObject { rotation: rotationParam ?? rotation, visible: visibleParam ?? visible, zIndex: zIndexParam ?? zIndex, + zIndexInt: zIndexIntParam ?? zIndexInt, onTap: onTapParam ?? onTap, onDragStart: onDragStartParam ?? onDragStart, onDrag: onDragParam ?? onDrag, @@ -301,6 +329,7 @@ class Marker implements MapsObject { addIfPresent('rotation', rotation); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); + addIfPresent('zIndexInt', zIndexInt); addIfPresent('clusterManagerId', clusterManagerId?.value); return json; } @@ -326,6 +355,7 @@ class Marker implements MapsObject { rotation == other.rotation && visible == other.visible && zIndex == other.zIndex && + zIndexInt == other.zIndexInt && clusterManagerId == other.clusterManagerId; } 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 27ce4594563..93df2f7f25e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.11.1 +version: 2.12.0 environment: sdk: ^3.6.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart index 76e5de2b186..35d16b4c7fa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; - import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { @@ -24,6 +23,7 @@ void main() { expect(marker.rotation, equals(0.0)); expect(marker.visible, equals(true)); expect(marker.zIndex, equals(0.0)); + expect(marker.zIndexInt, equals(0)); expect(marker.onTap, equals(null)); expect(marker.onDrag, equals(null)); expect(marker.onDragStart, equals(null)); @@ -60,7 +60,7 @@ void main() { position: const LatLng(50, 50), rotation: 100, visible: false, - zIndex: 100, + zIndexInt: 100, onTap: () {}, onDragStart: (LatLng latLng) {}, onDrag: (LatLng latLng) {}, @@ -86,6 +86,7 @@ void main() { 'rotation': 100.0, 'visible': false, 'zIndex': 100.0, + 'zIndexInt': 100, }); }); test('clone', () { @@ -169,5 +170,36 @@ void main() { copy.onDragEnd!(const LatLng(0, 1)); expect(log, contains('onDragEndParam')); }); + + test("Assert that both zIndex and zIndex int aren't passed in", () { + expect( + () => Marker( + markerId: const MarkerId('ABC123'), + zIndex: 5, + zIndexInt: 10, + ), + throwsAssertionError, + ); + }); + + test('zIndex param', () { + const Marker marker = Marker( + markerId: MarkerId('ABC123'), + zIndex: 5.00, + ); + + expect(marker.zIndexInt, 5); + expect(marker.zIndex, 5.00); + }); + + test('zIndexInt param', () { + const Marker marker = Marker( + markerId: MarkerId('ABC123'), + zIndexInt: 5, + ); + + expect(marker.zIndexInt, 5); + expect(marker.zIndex, 5.00); + }); }); }