From 2ed18ca97b985eb5a0440b52a16dc5e7d88c6df3 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Sat, 17 May 2025 02:11:37 +0000 Subject: [PATCH 1/3] adds easing animations to the zoom button controls --- .../androidify/camera/CameraScreenTest.kt | 24 +++---- .../androidify/camera/CameraControls.kt | 8 +-- .../androidify/camera/CameraScreen.kt | 29 +++++--- .../androidify/camera/CameraViewModel.kt | 3 +- .../androidify/camera/CameraViewfinder.kt | 16 ++--- .../androidify/camera/CameraZoomToolbar.kt | 18 ++--- .../developers/androidify/camera/ZoomState.kt | 72 +++++++++++++++++++ 7 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt diff --git a/feature/camera/src/androidTest/java/com/android/developers/androidify/camera/CameraScreenTest.kt b/feature/camera/src/androidTest/java/com/android/developers/androidify/camera/CameraScreenTest.kt index caa24e43..d76517ea 100644 --- a/feature/camera/src/androidTest/java/com/android/developers/androidify/camera/CameraScreenTest.kt +++ b/feature/camera/src/androidTest/java/com/android/developers/androidify/camera/CameraScreenTest.kt @@ -58,7 +58,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -79,7 +79,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -102,7 +102,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, ) } @@ -124,7 +124,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -145,7 +145,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -168,7 +168,7 @@ class CameraScreenTest { detectedPose = true, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -193,7 +193,7 @@ class CameraScreenTest { canFlipCamera = true, requestFlipCamera = {}, detectedPose = true, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -215,7 +215,7 @@ class CameraScreenTest { canFlipCamera = true, requestFlipCamera = {}, detectedPose = true, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -235,7 +235,7 @@ class CameraScreenTest { viewfinder = dummyViewfinder, defaultZoomOptions = zoomOptions, zoomLevel = { 1.0f }, // Start at second option to ensure first button click changes it - onChangeZoomLevel = { changedZoomLevel = it }, // Callback to test + onAnimateZoom = { changedZoomLevel = it }, // Callback to test // Default values for other params canFlipCamera = true, requestFlipCamera = {}, @@ -261,7 +261,7 @@ class CameraScreenTest { viewfinder = dummyViewfinder, defaultZoomOptions = zoomOptions, zoomLevel = { 0.6f }, // Start at first option - onChangeZoomLevel = { changedZoomLevel = it }, // Callback to test + onAnimateZoom = { changedZoomLevel = it }, // Callback to test // Default values for other params canFlipCamera = true, requestFlipCamera = {}, @@ -289,7 +289,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -310,7 +310,7 @@ class CameraScreenTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt index 2cb8aa81..ccc8b983 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraControls.kt @@ -35,7 +35,7 @@ internal fun CameraControls( detectedPose: Boolean, defaultZoomOptions: List, zoomLevel: () -> Float, - onChangeZoomLevel: (Float) -> Unit, + onZoomLevelSelected: (Float) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -45,7 +45,7 @@ internal fun CameraControls( ZoomToolbar( defaultZoomOptions = defaultZoomOptions, zoomLevel = zoomLevel, - onZoomLevelChanged = onChangeZoomLevel, + onZoomLevelSelected = onZoomLevelSelected ) Spacer(Modifier.height(12.dp)) Row(verticalAlignment = Alignment.CenterVertically) { @@ -74,8 +74,8 @@ private fun CameraControlsPreview() { canFlipCamera = true, flipCameraDirectionClicked = { }, detectedPose = true, - zoomLevel = { 0.4f }, - onChangeZoomLevel = { }, + zoomLevel = {0.4f}, + onZoomLevelSelected = {}, defaultZoomOptions = listOf(.6f, 1f), ) } diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraScreen.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraScreen.kt index fe2ad7d5..5b7eef35 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraScreen.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraScreen.kt @@ -130,6 +130,7 @@ fun CameraPreviewScreen( uiState.surfaceRequest?.let { surface -> CameraPreviewContent( + modifier = Modifier.fillMaxSize(), surfaceRequest = surface, autofocusUiState = uiState.autofocusUiState, tapToFocus = viewModel::tapToFocus, @@ -138,13 +139,14 @@ fun CameraPreviewScreen( requestFlipCamera = viewModel::flipCameraDirection, canFlipCamera = uiState.canFlipCamera, requestCaptureImage = viewModel::captureImage, + zoomRange = uiState.zoomMinRatio..uiState.zoomMaxRatio, zoomLevel = { uiState.zoomLevel }, onChangeZoomLevel = viewModel::setZoomLevel, foldingFeature = foldingFeature, - modifier = Modifier.fillMaxSize(), shouldShowRearCameraFeature = viewModel::shouldShowRearDisplayFeature, toggleRearCameraFeature = { viewModel.toggleRearDisplayFeature(activity) }, isRearCameraEnabled = uiState.isRearCameraActive, + cameraSessionId = uiState.cameraSessionId ) } } else { @@ -198,7 +200,7 @@ fun StatelessCameraPreviewContent( detectedPose: Boolean, defaultZoomOptions: List, zoomLevel: () -> Float, - onChangeZoomLevel: (Float) -> Unit, + onAnimateZoom: (Float) -> Unit, requestCaptureImage: () -> Unit, modifier: Modifier = Modifier, foldingFeature: FoldingFeature? = null, @@ -238,9 +240,9 @@ fun StatelessCameraPreviewContent( zoomButton = { zoomModifier -> ZoomToolbar( defaultZoomOptions = defaultZoomOptions, - zoomLevel = zoomLevel, - onZoomLevelChanged = onChangeZoomLevel, + onZoomLevelSelected = onAnimateZoom, modifier = zoomModifier, + zoomLevel = zoomLevel, ) }, guideText = { guideTextModifier -> @@ -287,12 +289,14 @@ private fun CameraPreviewContent( surfaceRequest: SurfaceRequest, autofocusUiState: AutofocusUiState, tapToFocus: (Offset) -> Unit, + cameraSessionId: Int, canFlipCamera: Boolean, requestFlipCamera: () -> Unit, detectedPose: Boolean, defaultZoomOptions: List, + zoomRange: ClosedFloatingPointRange, zoomLevel: () -> Float, - onChangeZoomLevel: (Float) -> Unit, + onChangeZoomLevel: (zoomLevel: Float) -> Unit, requestCaptureImage: () -> Unit, modifier: Modifier = Modifier, foldingFeature: FoldingFeature? = null, @@ -300,6 +304,14 @@ private fun CameraPreviewContent( toggleRearCameraFeature: () -> Unit = {}, isRearCameraEnabled: Boolean = false, ) { + val scope = rememberCoroutineScope() + val zoomState = remember(cameraSessionId) { + ZoomState( + initialZoomLevel = zoomLevel(), + onChangeZoomLevel = onChangeZoomLevel, + zoomRange = zoomRange, + ) + } // Delegate the layout to the stateless version StatelessCameraPreviewContent( viewfinder = { viewfinderModifier -> @@ -309,8 +321,7 @@ private fun CameraPreviewContent( surfaceRequest = surfaceRequest, autofocusUiState = autofocusUiState, tapToFocus = tapToFocus, - zoomLevel = zoomLevel, - onChangeZoomLevel = onChangeZoomLevel, + onScaleZoom = { scope.launch { zoomState.scaleZoom(it) }}, modifier = viewfinderModifier.onSizeChanged { size -> // Apply modifier from slot if (size.height > 0) { aspectRatio = calculateCorrectAspectRatio(size.height, size.width, aspectRatio) @@ -322,9 +333,9 @@ private fun CameraPreviewContent( canFlipCamera = canFlipCamera, requestFlipCamera = requestFlipCamera, detectedPose = detectedPose, - defaultZoomOptions = defaultZoomOptions, zoomLevel = zoomLevel, - onChangeZoomLevel = onChangeZoomLevel, + defaultZoomOptions = defaultZoomOptions, + onAnimateZoom = { scope.launch { zoomState.animatedZoom(it) } }, requestCaptureImage = requestCaptureImage, foldingFeature = foldingFeature, shouldShowRearCameraFeature = shouldShowRearCameraFeature, diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewModel.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewModel.kt index 736caec4..9a6ea969 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewModel.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewModel.kt @@ -148,7 +148,7 @@ class CameraViewModel processCameraProvider.runWith(cameraType, cameraUseCaseGroup) { camera -> cameraControl = camera.cameraControl cameraInfo = camera.cameraInfo - + _uiState.update { it.copy(cameraSessionId = it.cameraSessionId + 1) } // Suspend on zoom updates camera.cameraInfo.zoomState.asFlow().collectLatest { zoomState -> _uiState.update { @@ -342,6 +342,7 @@ class CameraViewModel */ data class CameraUiState( val surfaceRequest: SurfaceRequest? = null, + val cameraSessionId: Int = 0, val imageUri: Uri? = null, val detectedPose: Boolean = false, val zoomMaxRatio: Float = 1f, diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewfinder.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewfinder.kt index 6a1f159b..7a614c35 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewfinder.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraViewfinder.kt @@ -47,16 +47,14 @@ private val TAP_TO_FOCUS_INDICATOR_SIZE = 48.dp @Composable internal fun CameraViewfinder( - surfaceRequest: SurfaceRequest, - autofocusUiState: AutofocusUiState, - tapToFocus: (tapCoords: Offset) -> Unit, - zoomLevel: () -> Float, - onChangeZoomLevel: (zoomLevel: Float) -> Unit, - modifier: Modifier = Modifier, + surfaceRequest: SurfaceRequest, + autofocusUiState: AutofocusUiState, + tapToFocus: (tapCoords: Offset) -> Unit, + onScaleZoom: (zoomScaleFactor: Float) -> Unit, + modifier: Modifier = Modifier, ) { + val onScaleCurrentZoom by rememberUpdatedState(onScaleZoom) val currentTapToFocus by rememberUpdatedState(tapToFocus) - val currentOnChangeZoomLevel by rememberUpdatedState(onChangeZoomLevel) - val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = surfaceRequest, @@ -72,7 +70,7 @@ internal fun CameraViewfinder( .transformable( rememberTransformableState( onTransformation = { zoomChange, _, _ -> - currentOnChangeZoomLevel(zoomChange * zoomLevel()) + onScaleCurrentZoom(zoomChange) }, ), ), diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt index af6c17ef..3fa0b84d 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/CameraZoomToolbar.kt @@ -46,7 +46,7 @@ import kotlin.math.roundToInt internal fun ZoomToolbar( defaultZoomOptions: List, zoomLevel: () -> Float, - onZoomLevelChanged: (Float) -> Unit, + onZoomLevelSelected: (Float) -> Unit, modifier: Modifier = Modifier, ) { // Only render the zoom toolbar when there's exactly two options @@ -87,7 +87,7 @@ internal fun ZoomToolbar( ) { ToggleButton( checked = selectedOptionIndex == 0, - onCheckedChange = { onZoomLevelChanged(defaultZoomOptions[0]) }, + onCheckedChange = { onZoomLevelSelected(defaultZoomOptions[0]) }, shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(), colors = ToggleButtonDefaults.toggleButtonColors(), modifier = Modifier, @@ -98,7 +98,7 @@ internal fun ZoomToolbar( } ToggleButton( checked = selectedOptionIndex == 1, - onCheckedChange = { onZoomLevelChanged(defaultZoomOptions[1]) }, + onCheckedChange = { onZoomLevelSelected(defaultZoomOptions[1]) }, shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(), colors = ToggleButtonDefaults.toggleButtonColors(), modifier = Modifier, @@ -120,24 +120,18 @@ private fun ZoomToolbarPreview() { ZoomToolbar( defaultZoomOptions = listOf(0.6f, 1f), zoomLevel = { zoomLevel }, - onZoomLevelChanged = { - zoomLevel = it - }, + onZoomLevelSelected = { zoomLevel = it }, ) ZoomToolbar( defaultZoomOptions = listOf(1f, 2f), zoomLevel = { zoomLevel }, - onZoomLevelChanged = { - zoomLevel = it - }, + onZoomLevelSelected = { zoomLevel = it }, ) // Doesn't render ZoomToolbar( defaultZoomOptions = listOf(1f), zoomLevel = { zoomLevel }, - onZoomLevelChanged = { - zoomLevel = it - }, + onZoomLevelSelected = { zoomLevel = it }, ) Row { Button(onClick = { zoomLevel -= 0.1f }) { diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt new file mode 100644 index 00000000..af4c4d35 --- /dev/null +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.developers.androidify.camera + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.tween +import androidx.compose.foundation.MutatorMutex +import androidx.compose.runtime.Stable + +@Stable +class ZoomState( + initialZoomLevel: Float, + val zoomRange: ClosedFloatingPointRange, + val onChangeZoomLevel: (Float) -> Unit, + ) { + private var functionalZoom = initialZoomLevel + + private val mutatorMutex = MutatorMutex() + + /** + * Immediately set the current zoom level to [targetZoomLevel]. + */ + suspend fun absoluteZoom(targetZoomLevel: Float) { + mutatorMutex.mutate { + functionalZoom = targetZoomLevel.coerceIn(zoomRange) + onChangeZoomLevel(functionalZoom) + } + } + + /** + * Scale the current zoom level. + */ + suspend fun scaleZoom(scalingFactor: Float) { + absoluteZoom(scalingFactor * functionalZoom) + } + + /** + * Ease towards a specific zoom level + * + * @param animationSpec [AnimationSpec] used for the animation, default to tween over 500ms + */ + suspend fun animatedZoom( + targetZoomLevel: Float, + animationSpec: AnimationSpec = tween(durationMillis = 500), + ) { + mutatorMutex.mutate { + Animatable(initialValue = functionalZoom).animateTo( + targetValue = targetZoomLevel, + animationSpec = animationSpec, + ) { + // this is called every animation frame + functionalZoom = value.coerceIn(zoomRange) + onChangeZoomLevel(functionalZoom) + } + } + } +} \ No newline at end of file From 9896e54cceea07c1ede1b9a72896c9aebe70bd0a Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Sat, 17 May 2025 13:05:07 +0000 Subject: [PATCH 2/3] update CameraScreenshotTest --- .../camera/CameraScreenScreenshotTest.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/feature/camera/src/screenshotTest/java/com/android/developers/androidify/camera/CameraScreenScreenshotTest.kt b/feature/camera/src/screenshotTest/java/com/android/developers/androidify/camera/CameraScreenScreenshotTest.kt index 66f73c95..1c4ce648 100644 --- a/feature/camera/src/screenshotTest/java/com/android/developers/androidify/camera/CameraScreenScreenshotTest.kt +++ b/feature/camera/src/screenshotTest/java/com/android/developers/androidify/camera/CameraScreenScreenshotTest.kt @@ -61,7 +61,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -78,7 +78,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -95,7 +95,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -112,7 +112,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, shouldShowRearCameraFeature = { true }, // Changed isRearCameraEnabled = false, // Changed @@ -132,7 +132,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, shouldShowRearCameraFeature = { true }, // Changed isRearCameraEnabled = true, // Changed @@ -156,7 +156,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, foldingFeature = tabletopFoldingFeature, // Changed ) @@ -174,7 +174,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } @@ -191,7 +191,7 @@ class CameraScreenScreenshotTest { requestFlipCamera = {}, defaultZoomOptions = listOf(1f), zoomLevel = { 1f }, - onChangeZoomLevel = {}, + onAnimateZoom = {}, requestCaptureImage = {}, ) } From cf0058107d2af103a094aad1ca52321d72d9b56f Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Sun, 18 May 2025 16:03:52 +0000 Subject: [PATCH 3/3] update zoomstate indentation --- .../developers/androidify/camera/ZoomState.kt | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt b/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt index af4c4d35..506f521a 100644 --- a/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt +++ b/feature/camera/src/main/java/com/android/developers/androidify/camera/ZoomState.kt @@ -24,49 +24,49 @@ import androidx.compose.runtime.Stable @Stable class ZoomState( - initialZoomLevel: Float, - val zoomRange: ClosedFloatingPointRange, - val onChangeZoomLevel: (Float) -> Unit, - ) { - private var functionalZoom = initialZoomLevel + initialZoomLevel: Float, + val zoomRange: ClosedFloatingPointRange, + val onChangeZoomLevel: (Float) -> Unit, +) { + private var functionalZoom = initialZoomLevel - private val mutatorMutex = MutatorMutex() + private val mutatorMutex = MutatorMutex() - /** - * Immediately set the current zoom level to [targetZoomLevel]. - */ - suspend fun absoluteZoom(targetZoomLevel: Float) { - mutatorMutex.mutate { - functionalZoom = targetZoomLevel.coerceIn(zoomRange) - onChangeZoomLevel(functionalZoom) + /** + * Immediately set the current zoom level to [targetZoomLevel]. + */ + suspend fun absoluteZoom(targetZoomLevel: Float) { + mutatorMutex.mutate { + functionalZoom = targetZoomLevel.coerceIn(zoomRange) + onChangeZoomLevel(functionalZoom) + } } - } - /** - * Scale the current zoom level. - */ - suspend fun scaleZoom(scalingFactor: Float) { - absoluteZoom(scalingFactor * functionalZoom) - } + /** + * Scale the current zoom level. + */ + suspend fun scaleZoom(scalingFactor: Float) { + absoluteZoom(scalingFactor * functionalZoom) + } - /** - * Ease towards a specific zoom level - * - * @param animationSpec [AnimationSpec] used for the animation, default to tween over 500ms - */ - suspend fun animatedZoom( - targetZoomLevel: Float, - animationSpec: AnimationSpec = tween(durationMillis = 500), - ) { - mutatorMutex.mutate { - Animatable(initialValue = functionalZoom).animateTo( - targetValue = targetZoomLevel, - animationSpec = animationSpec, - ) { - // this is called every animation frame - functionalZoom = value.coerceIn(zoomRange) - onChangeZoomLevel(functionalZoom) - } + /** + * Ease towards a specific zoom level + * + * @param animationSpec [AnimationSpec] used for the animation, default to tween over 500ms + */ + suspend fun animatedZoom( + targetZoomLevel: Float, + animationSpec: AnimationSpec = tween(durationMillis = 500), + ) { + mutatorMutex.mutate { + Animatable(initialValue = functionalZoom).animateTo( + targetValue = targetZoomLevel, + animationSpec = animationSpec, + ) { + // this is called every animation frame + functionalZoom = value.coerceIn(zoomRange) + onChangeZoomLevel(functionalZoom) + } + } } - } } \ No newline at end of file