diff --git a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraUseCase.kt b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraUseCase.kt index 2f9e5866..86184c86 100644 --- a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraUseCase.kt +++ b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraUseCase.kt @@ -48,8 +48,12 @@ interface CameraUseCase { fun stopVideoRecording() - fun setZoomScale(scale: Float) + fun setZoomScale(scale: Float): Float fun setFlashMode(flashModeStatus: FlashModeStatus) suspend fun flipCamera(isFrontFacing: Boolean) + + companion object { + const val INVALID_ZOOM_SCALE = -1f + } } \ No newline at end of file diff --git a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt index 1bd672e6..2bbe4fed 100644 --- a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt +++ b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/CameraXCameraUseCase.kt @@ -39,6 +39,7 @@ import androidx.camera.video.VideoCapture import androidx.concurrent.futures.await import androidx.core.content.ContextCompat import androidx.core.util.Consumer +import com.google.jetpackcamera.domain.camera.CameraUseCase.Companion.INVALID_ZOOM_SCALE import com.google.jetpackcamera.settings.SettingsRepository import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.FlashModeStatus @@ -168,11 +169,12 @@ class CameraXCameraUseCase @Inject constructor( recording?.stop() } - override fun setZoomScale(scale: Float) { - val zoomState = getZoomState() ?: return + override fun setZoomScale(scale: Float): Float { + val zoomState = getZoomState() ?: return INVALID_ZOOM_SCALE val finalScale = (zoomState.zoomRatio * scale).coerceIn(zoomState.minZoomRatio, zoomState.maxZoomRatio) camera?.cameraControl?.setZoomRatio(finalScale) + return finalScale } private fun getZoomState(): ZoomState? = camera?.cameraInfo?.zoomState?.value diff --git a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/test/FakeCameraUseCase.kt b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/test/FakeCameraUseCase.kt index 835b438c..87d543a4 100644 --- a/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/test/FakeCameraUseCase.kt +++ b/domain/camera/src/main/java/com/google/jetpackcamera/domain/camera/test/FakeCameraUseCase.kt @@ -78,7 +78,8 @@ class FakeCameraUseCase : CameraUseCase { recordingInProgress = false } - override fun setZoomScale(scale: Float) { + override fun setZoomScale(scale: Float): Float { + return -1f } override fun setFlashMode(flashModeStatus: FlashModeStatus) { diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt index 864ae59d..d0bb854b 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt @@ -16,8 +16,12 @@ package com.google.jetpackcamera.feature.preview +import android.os.Handler +import android.os.Looper import android.util.Log import androidx.camera.core.Preview.SurfaceProvider +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTapGestures @@ -25,6 +29,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -40,14 +45,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -77,8 +86,13 @@ fun PreviewScreen( Log.d(TAG, "onSurfaceProviderReady") deferredSurfaceProvider.complete(it) } + var zoomScale by remember { mutableStateOf(1f) } + var zoomScaleShow by remember { mutableStateOf(false) } + val zoomHandler = Handler(Looper.getMainLooper()) val transformableState = rememberTransformableState(onTransformation = { zoomChange, _, _ -> - viewModel.setZoomScale(zoomChange) + zoomScale = viewModel.setZoomScale(zoomChange) + zoomScaleShow = true + zoomHandler.postDelayed({ zoomScaleShow = false }, 3000) }) LaunchedEffect(lifecycleOwner) { @@ -97,7 +111,9 @@ fun PreviewScreen( Text(text = stringResource(R.string.camera_not_ready)) } else if (previewUiState.cameraState == CameraState.READY) { Box( - modifier = Modifier.fillMaxSize().transformable(state = transformableState) + modifier = Modifier + .fillMaxSize() + .transformable(state = transformableState) ) { CameraPreview( modifier = Modifier @@ -140,23 +156,45 @@ fun PreviewScreen( ) } - Row( - modifier = Modifier.align(Alignment.BottomCenter), - horizontalArrangement = Arrangement.SpaceAround + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.align(Alignment.BottomCenter) ) { - Row { - CaptureButton( - onClick = { viewModel.captureImage() }, - onLongPress = { viewModel.startVideoRecording() }, - onRelease = { viewModel.stopVideoRecording() }, - state = previewUiState.videoRecordingState - ) + ZoomScaleText( + zoomScale = zoomScale, + show = zoomScaleShow + ) + Row( + horizontalArrangement = Arrangement.SpaceAround + ) { + Row { + CaptureButton( + onClick = { viewModel.captureImage() }, + onLongPress = { viewModel.startVideoRecording() }, + onRelease = { viewModel.stopVideoRecording() }, + state = previewUiState.videoRecordingState + ) + } } } } } } +@Composable +fun ZoomScaleText(zoomScale: Float, show: Boolean) { + val contentAlpha = animateFloatAsState( + targetValue = if (show) 1f else 0f, label = "zoomScaleAlphaAnimation", + animationSpec = tween() + ) + Text( + modifier = Modifier.alpha(contentAlpha.value), + text = String.format("%.1fx", zoomScale), + fontSize = 20.sp, + color = Color.White + ) +} + @Composable fun CaptureButton( onClick: () -> Unit, diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt index 3bb57702..724402d0 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt @@ -180,8 +180,8 @@ class PreviewViewModel @Inject constructor( recordingJob?.cancel() } - fun setZoomScale(scale: Float) { - cameraUseCase.setZoomScale(scale = scale) + fun setZoomScale(scale: Float): Float { + return cameraUseCase.setZoomScale(scale = scale) } // modify ui values