Skip to content

Commit

Permalink
Use camera-compose and restore double-tap to flip camera (#263)
Browse files Browse the repository at this point in the history
* Move HDR color mode code out of CameraXViewfinder

* Replace CameraXViewfinder with camera-compose version

* Move double tap logic to same pointerInput as tapToFocus
  • Loading branch information
temcguir authored Sep 13, 2024
1 parent d7942c6 commit e5fb46b
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 220 deletions.
2 changes: 1 addition & 1 deletion feature/preview/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ dependencies {

// CameraX
implementation(libs.camera.core)
implementation(libs.camera.viewfinder.compose)
implementation(libs.camera.compose)

// Hilt
implementation(libs.dagger.hilt.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -203,15 +204,11 @@ private fun ContentScreen(
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) {
val lensFacing = remember(previewUiState) {
val lensFacing by rememberUpdatedState(
previewUiState.currentCameraSettings.cameraLensFacing
}
)

val onFlipCamera = remember(lensFacing) {
{
onSetLensFacing(lensFacing.flip())
}
}
val onFlipCamera = { onSetLensFacing(lensFacing.flip()) }

val isMuted = remember(previewUiState) {
previewUiState.currentCameraSettings.audioMuted
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
*/
package com.google.jetpackcamera.feature.preview.ui

import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.camera.compose.CameraXViewfinder
import androidx.camera.core.DynamicRange as CXDynamicRange
import androidx.camera.core.SurfaceRequest
import androidx.camera.viewfinder.compose.MutableCoordinateTransformer
import androidx.camera.viewfinder.surface.ImplementationMode
import androidx.compose.animation.core.EaseOutExpo
import androidx.compose.animation.core.LinearEasing
Expand Down Expand Up @@ -51,7 +55,6 @@ import androidx.compose.material.icons.filled.CameraAlt
import androidx.compose.material.icons.filled.FlipCameraAndroid
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.MicOff
import androidx.compose.material.icons.filled.Nightlight
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.VideoStable
import androidx.compose.material.icons.filled.Videocam
Expand All @@ -75,6 +78,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
Expand All @@ -98,6 +102,10 @@ import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.LowLightBoost
import com.google.jetpackcamera.settings.model.Stabilization
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch

private const val TAG = "PreviewScreen"
Expand Down Expand Up @@ -264,24 +272,12 @@ fun PreviewDisplay(
}
)

val currentOnFlipCamera by rememberUpdatedState(onFlipCamera)

surfaceRequest?.let {
BoxWithConstraints(
Modifier
.testTag(PREVIEW_DISPLAY)
.fillMaxSize()
.background(Color.Black)
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = { offset ->
// double tap to flip camera
Log.d(TAG, "onDoubleTap $offset")
currentOnFlipCamera()
}
)
},

.background(Color.Black),
contentAlignment = Alignment.Center
) {
val maxAspectRatio: Float = maxWidth / maxHeight
Expand Down Expand Up @@ -316,21 +312,94 @@ fun PreviewDisplay(
.alpha(imageAlpha)
.clip(RoundedCornerShape(16.dp))
) {
val implementationMode = when {
Build.VERSION.SDK_INT > 24 -> ImplementationMode.EXTERNAL
else -> ImplementationMode.EMBEDDED
}

DetectWindowColorModeChanges(
surfaceRequest = surfaceRequest,
implementationMode = implementationMode,
onRequestWindowColorMode = onRequestWindowColorMode
)

val coordinateTransformer = remember { MutableCoordinateTransformer() }
CameraXViewfinder(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = { offset ->
// double tap to flip camera
Log.d(TAG, "onDoubleTap $offset")
onFlipCamera()
},
onTap = {
with(coordinateTransformer) {
val surfaceCoords = it.transform()
Log.d(
"TAG",
"onTapToFocus: " +
"input{$it} -> surface{$surfaceCoords}"
)
onTapToFocus(surfaceCoords.x, surfaceCoords.y)
}
}
)
},
surfaceRequest = it,
implementationMode = when {
Build.VERSION.SDK_INT > 24 -> ImplementationMode.EXTERNAL
else -> ImplementationMode.EMBEDDED
},
onRequestWindowColorMode = onRequestWindowColorMode,
onTap = { x, y -> onTapToFocus(x, y) }
implementationMode = implementationMode,
coordinateTransformer = coordinateTransformer
)
}
}
}
}

@Composable
fun DetectWindowColorModeChanges(
surfaceRequest: SurfaceRequest,
implementationMode: ImplementationMode,
onRequestWindowColorMode: (Int) -> Unit
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val currentSurfaceRequest: SurfaceRequest by rememberUpdatedState(surfaceRequest)
val currentImplementationMode: ImplementationMode by rememberUpdatedState(
implementationMode
)
val currentOnRequestWindowColorMode: (Int) -> Unit by rememberUpdatedState(
onRequestWindowColorMode
)

LaunchedEffect(Unit) {
val colorModeSnapshotFlow =
snapshotFlow { Pair(currentSurfaceRequest.dynamicRange, currentImplementationMode) }
.map { (dynamicRange, implMode) ->
val isSourceHdr = dynamicRange.encoding != CXDynamicRange.ENCODING_SDR
val destSupportsHdr = implMode == ImplementationMode.EXTERNAL
if (isSourceHdr && destSupportsHdr) {
ActivityInfo.COLOR_MODE_HDR
} else {
ActivityInfo.COLOR_MODE_DEFAULT
}
}.distinctUntilChanged()

val callbackSnapshotFlow = snapshotFlow { currentOnRequestWindowColorMode }

// Combine both flows so that we call the callback every time it changes or the
// window color mode changes.
// We'll also reset to default when this LaunchedEffect is disposed
combine(colorModeSnapshotFlow, callbackSnapshotFlow) { colorMode, callback ->
Pair(colorMode, callback)
}.onCompletion {
currentOnRequestWindowColorMode(ActivityInfo.COLOR_MODE_DEFAULT)
}.collect { (colorMode, callback) ->
callback(colorMode)
}
}
}
}

@Composable
fun StabilizationIcon(
videoStabilization: Stabilization,
Expand Down
Loading

0 comments on commit e5fb46b

Please sign in to comment.