summaryrefslogtreecommitdiff
path: root/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
diff options
context:
space:
mode:
Diffstat (limited to 'feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt')
-rw-r--r--feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt248
1 files changed, 248 insertions, 0 deletions
diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
new file mode 100644
index 0000000..a952bcf
--- /dev/null
+++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/PreviewScreenComponents.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.jetpackcamera.feature.preview.ui
+
+import android.util.Log
+import android.view.Display
+import android.view.View
+import androidx.camera.core.Preview
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.rememberTransformableState
+import androidx.compose.foundation.gestures.transformable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.jetpackcamera.feature.preview.R
+import com.google.jetpackcamera.feature.preview.VideoRecordingState
+import com.google.jetpackcamera.settings.model.AspectRatio
+import com.google.jetpackcamera.viewfinder.CameraPreview
+import kotlinx.coroutines.CompletableDeferred
+
+private const val TAG = "PreviewScreen"
+
+/** this is the preview surface display. This view implements gestures tap to focus, pinch to zoom,
+ * and double tap to flip camera */
+@Composable
+fun PreviewDisplay(
+ onTapToFocus: (Display, Int, Int, Float, Float) -> Unit,
+ onFlipCamera: () -> Unit,
+ onZoomChange: (Float) -> Unit,
+ aspectRatio: AspectRatio,
+ deferredSurfaceProvider: CompletableDeferred<Preview.SurfaceProvider>
+) {
+ val transformableState = rememberTransformableState(
+ onTransformation = { zoomChange, _, _ ->
+ onZoomChange(zoomChange)
+ }
+ )
+ val onSurfaceProviderReady: (Preview.SurfaceProvider) -> Unit = {
+ Log.d(TAG, "onSurfaceProviderReady")
+ deferredSurfaceProvider.complete(it)
+ }
+ lateinit var viewInfo: View
+
+ BoxWithConstraints(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Black)
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onDoubleTap = { offset ->
+ // double tap to flip camera
+ Log.d(TAG, "onDoubleTap $offset")
+ onFlipCamera()
+ },
+ onTap = { offset ->
+ // tap to focus
+ try {
+ onTapToFocus(
+ viewInfo.display,
+ viewInfo.width,
+ viewInfo.height,
+ offset.x,
+ offset.y
+ )
+ Log.d(TAG, "onTap $offset")
+ } catch (e: UninitializedPropertyAccessException) {
+ Log.d(TAG, "onTap $offset")
+ e.printStackTrace()
+ }
+ }
+ )
+ },
+
+ contentAlignment = Alignment.Center
+ ) {
+ val maxAspectRatio: Float = maxWidth / maxHeight
+ val aspectRatioFloat: Float = aspectRatio.ratio.toFloat()
+ val shouldUseMaxWidth = maxAspectRatio <= aspectRatioFloat
+ val width = if (shouldUseMaxWidth) maxWidth else maxHeight * aspectRatioFloat
+ val height = if (!shouldUseMaxWidth) maxHeight else maxWidth / aspectRatioFloat
+ Box(
+ modifier = Modifier
+ .width(width)
+ .height(height)
+ .transformable(state = transformableState)
+
+ ) {
+ CameraPreview(
+ modifier = Modifier
+ .fillMaxSize(),
+ onSurfaceProviderReady = onSurfaceProviderReady,
+ onRequestBitmapReady = {
+ it.invoke()
+ },
+ setSurfaceView = { s: View ->
+ viewInfo = s
+ }
+ )
+ }
+ }
+}
+
+/**
+ * A temporary button that can be added to preview for quick testing purposes
+ */
+@Composable
+fun TestingButton(modifier: Modifier = Modifier, onClick: () -> Unit, text: String) {
+ SuggestionChip(
+ onClick = { onClick() },
+ modifier = modifier,
+ label = {
+ Text(text = text)
+ }
+ )
+}
+
+@Composable
+fun FlipCameraButton(
+ modifier: Modifier = Modifier,
+ enabledCondition: Boolean,
+ onClick: () -> Unit
+) {
+ Box(modifier = modifier) {
+ IconButton(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(40.dp),
+ onClick = onClick,
+ enabled = enabledCondition
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Refresh,
+ tint = Color.White,
+ contentDescription = stringResource(id = R.string.flip_camera_content_description),
+ modifier = Modifier.size(72.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun SettingsNavButton(modifier: Modifier, onNavigateToSettings: () -> Unit) {
+ IconButton(
+ modifier = modifier,
+ onClick = onNavigateToSettings
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Settings,
+ tint = Color.White,
+ contentDescription = stringResource(R.string.settings_content_description),
+ modifier = Modifier.size(72.dp)
+ )
+ }
+}
+
+@Composable
+fun ZoomScaleText(zoomScale: Float) {
+ val contentAlpha = animateFloatAsState(
+ targetValue = 10f,
+ label = "zoomScaleAlphaAnimation",
+ animationSpec = tween()
+ )
+ Text(
+ modifier = Modifier.alpha(contentAlpha.value),
+ text = "%.1fx".format(zoomScale),
+ fontSize = 20.sp,
+ color = Color.White
+ )
+}
+
+@Composable
+fun CaptureButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ onLongPress: () -> Unit,
+ onRelease: () -> Unit,
+ videoRecordingState: VideoRecordingState
+) {
+ Box(
+ modifier = modifier
+ .fillMaxHeight()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ onLongPress()
+ },
+ onPress = {
+ awaitRelease()
+ onRelease()
+ },
+ onTap = { onClick() }
+ )
+ }
+ .size(120.dp)
+ .padding(18.dp)
+ .border(4.dp, Color.White, CircleShape)
+ ) {
+ Canvas(modifier = Modifier.size(110.dp), onDraw = {
+ drawCircle(
+ color =
+ when (videoRecordingState) {
+ VideoRecordingState.INACTIVE -> Color.Transparent
+ VideoRecordingState.ACTIVE -> Color.Red
+ }
+ )
+ })
+ }
+}