From a513041934c6f21c3ab0a0a63e422795f3ef6f3e Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Wed, 18 Dec 2024 18:23:04 +0000 Subject: [PATCH 1/3] WIP capturebutton + new capture state + capture button uistate --- .../feature/preview/PreviewScreen.kt | 4 +- .../feature/preview/PreviewUiState.kt | 19 +- .../feature/preview/PreviewViewModel.kt | 21 ++- .../quicksettings/QuickSettingsScreen.kt | 22 +-- .../preview/ui/CameraControlsOverlay.kt | 172 +++++++++--------- .../preview/ui/PreviewScreenComponents.kt | 99 +++++++--- 6 files changed, 215 insertions(+), 122 deletions(-) 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 49acf8f73..250a2592c 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 @@ -51,7 +51,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleStartEffect import androidx.tracing.Trace -import com.google.jetpackcamera.core.camera.VideoRecordingState import com.google.jetpackcamera.feature.preview.quicksettings.QuickSettingsScreenOverlay import com.google.jetpackcamera.feature.preview.ui.CameraControlsOverlay import com.google.jetpackcamera.feature.preview.ui.PreviewDisplay @@ -145,7 +144,7 @@ fun PreviewScreen( onSetCaptureMode = viewModel::setCaptureMode, onChangeFlash = viewModel::setFlash, onChangeAspectRatio = viewModel::setAspectRatio, - onChangeCaptureMode = viewModel::setCaptureMode, + onChangeCaptureMode = viewModel::setStreamConfig, onChangeDynamicRange = viewModel::setDynamicRange, onChangeConcurrentCameraMode = viewModel::setConcurrentCameraMode, onChangeImageFormat = viewModel::setImageFormat, @@ -347,7 +346,6 @@ private fun ContentScreen_WhileRecording() { private val FAKE_PREVIEW_UI_STATE_READY = PreviewUiState.Ready( currentCameraSettings = DEFAULT_CAMERA_APP_SETTINGS, - videoRecordingState = VideoRecordingState.Inactive(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {} // captureModeToggleUiState = CaptureModeToggleUiState.Invisible diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewUiState.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewUiState.kt index 714b1731f..db30d1fe8 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewUiState.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewUiState.kt @@ -19,6 +19,7 @@ import com.google.jetpackcamera.core.camera.VideoRecordingState import com.google.jetpackcamera.feature.preview.ui.SnackbarData import com.google.jetpackcamera.feature.preview.ui.ToastMessage import com.google.jetpackcamera.settings.model.CameraAppSettings +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.FlashMode import com.google.jetpackcamera.settings.model.StabilizationMode import com.google.jetpackcamera.settings.model.SystemConstraints @@ -50,7 +51,8 @@ sealed interface PreviewUiState { val debugUiState: DebugUiState = DebugUiState(), val stabilizationUiState: StabilizationUiState = StabilizationUiState.Disabled, val flashModeUiState: FlashModeUiState = FlashModeUiState.Unavailable, - val captureModeUiState: CaptureModeUiState = CaptureModeUiState.Unavailable + val captureModeUiState: CaptureModeUiState = CaptureModeUiState.Unavailable, + val captureButtonUiState: CaptureButtonUiState = CaptureButtonUiState.Unavailable ) : PreviewUiState } @@ -61,7 +63,20 @@ data class DebugUiState( val isDebugMode: Boolean = false, val isDebugOverlayOpen: Boolean = false ) - +val DEFAULT_CAPTURE_BUTTON_UISTATE = CaptureButtonUiState.Enabled( + captureMode = CaptureMode.DEFAULT, + previewMode = PreviewMode.StandardMode {}, + videoRecordingState = VideoRecordingState.Inactive() +) +sealed interface CaptureButtonUiState { + + data object Unavailable : CaptureButtonUiState + data class Enabled( + val captureMode: CaptureMode, + val previewMode: PreviewMode, + val videoRecordingState: VideoRecordingState + ) : CaptureButtonUiState +} sealed interface StabilizationUiState { data object Disabled : StabilizationUiState 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 bf14ee3f5..238307852 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 @@ -185,7 +185,7 @@ class PreviewViewModel @AssistedInject constructor( }.copy( // Update or initialize PreviewUiState.Ready previewMode = previewMode, - currentCameraSettings = cameraAppSettings, + currentCameraSettings = cameraAppSettings.applyPreviewMode(previewMode), systemConstraints = systemConstraints, zoomScale = cameraState.zoomScale, videoRecordingState = cameraState.videoRecordingState, @@ -208,6 +208,10 @@ class PreviewViewModel @AssistedInject constructor( captureModeUiState = getCaptureModeUiState( systemConstraints, cameraAppSettings + ), + captureButtonUiState = getCaptureButtonUiState( + cameraAppSettings, + cameraState ) // TODO(kc): set elapsed time UI state once VideoRecordingState // refactor is complete. @@ -354,6 +358,8 @@ class PreviewViewModel @AssistedInject constructor( systemConstraints: SystemConstraints, cameraAppSettings: CameraAppSettings ): CaptureModeUiState { + Log.d(TAG, "new capture mode state ${cameraAppSettings.captureMode}") + val cameraConstraints: CameraConstraints? = systemConstraints.forCurrentLens( cameraAppSettings ) @@ -531,6 +537,17 @@ class PreviewViewModel @AssistedInject constructor( } } + fun getCaptureButtonUiState( + cameraAppSettings: CameraAppSettings, + cameraState: CameraState + ): CaptureButtonUiState { + Log.d(TAG, "new capture button state ${cameraAppSettings.captureMode}") + return CaptureButtonUiState.Enabled( + captureMode = cameraAppSettings.captureMode, + previewMode = previewMode, + videoRecordingState = cameraState.videoRecordingState + ) + } /* private fun getCaptureToggleUiState( systemConstraints: SystemConstraints, @@ -732,7 +749,7 @@ class PreviewViewModel @AssistedInject constructor( } } - fun setCaptureMode(streamConfig: StreamConfig) { + fun setStreamConfig(streamConfig: StreamConfig) { viewModelScope.launch { cameraUseCase.setStreamConfig(streamConfig) } diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/quicksettings/QuickSettingsScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/quicksettings/QuickSettingsScreen.kt index adca80b2f..4e84ec541 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/quicksettings/QuickSettingsScreen.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/quicksettings/QuickSettingsScreen.kt @@ -39,6 +39,8 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.tooling.preview.Preview import com.google.jetpackcamera.core.camera.VideoRecordingState +import com.google.jetpackcamera.feature.preview.CaptureButtonUiState +import com.google.jetpackcamera.feature.preview.DEFAULT_CAPTURE_BUTTON_UISTATE import com.google.jetpackcamera.feature.preview.FlashModeUiState import com.google.jetpackcamera.feature.preview.PreviewMode import com.google.jetpackcamera.feature.preview.PreviewUiState @@ -60,6 +62,7 @@ import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsGr import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.CameraConstraints +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.ConcurrentCameraMode import com.google.jetpackcamera.settings.model.DynamicRange import com.google.jetpackcamera.settings.model.FlashMode @@ -294,14 +297,16 @@ fun ExpandedQuickSettingsUiPreview() { MaterialTheme { ExpandedQuickSettingsUi( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, - videoRecordingState = VideoRecordingState.Inactive(), - // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, flashModeUiState = FlashModeUiState.Available( selectedFlashMode = FlashMode.OFF, availableFlashModes = listOf(FlashMode.OFF, FlashMode.ON) + ), + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode({}), + + VideoRecordingState.Inactive() ) ), currentCameraSettings = CameraAppSettings(), @@ -324,11 +329,8 @@ fun ExpandedQuickSettingsUiPreview_WithHdr() { MaterialTheme { ExpandedQuickSettingsUi( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, - // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Inactive() + captureButtonUiState = DEFAULT_CAPTURE_BUTTON_UISTATE ), currentCameraSettings = CameraAppSettings(dynamicRange = DynamicRange.HLG10), onLensFaceClick = { }, @@ -346,9 +348,7 @@ fun ExpandedQuickSettingsUiPreview_WithHdr() { private val TYPICAL_SYSTEM_CONSTRAINTS_WITH_HDR = TYPICAL_SYSTEM_CONSTRAINTS.copy( - perLensConstraints = TYPICAL_SYSTEM_CONSTRAINTS.perLensConstraints.entries.associate { - (lensFacing, constraints) - -> + perLensConstraints = TYPICAL_SYSTEM_CONSTRAINTS.perLensConstraints.entries.associate { (lensFacing, constraints) -> lensFacing to constraints.copy( supportedDynamicRanges = setOf(DynamicRange.SDR, DynamicRange.HLG10) ) diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/CameraControlsOverlay.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/CameraControlsOverlay.kt index 81c7648cf..608e946d1 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/CameraControlsOverlay.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/CameraControlsOverlay.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.jetpackcamera.core.camera.VideoRecordingState +import com.google.jetpackcamera.feature.preview.CaptureButtonUiState import com.google.jetpackcamera.feature.preview.CaptureModeUiState import com.google.jetpackcamera.feature.preview.DisabledReason import com.google.jetpackcamera.feature.preview.FlashModeUiState @@ -59,7 +60,6 @@ import com.google.jetpackcamera.feature.preview.StabilizationUiState import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsIndicators import com.google.jetpackcamera.feature.preview.quicksettings.ui.ToggleQuickSettingsButton import com.google.jetpackcamera.feature.preview.ui.debug.DebugOverlayToggleButton -import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.FlashMode import com.google.jetpackcamera.settings.model.ImageOutputFormat @@ -330,9 +330,8 @@ private fun ControlsBottom( } } CaptureButton( - previewUiState = previewUiState, + captureButtonUiState = previewUiState.captureButtonUiState, isQuickSettingsOpen = isQuickSettingsOpen, - videoRecordingState = videoRecordingState, onCaptureImageWithUri = onCaptureImageWithUri, onToggleQuickSettings = onToggleQuickSettings, onStartVideoRecording = onStartVideoRecording, @@ -368,17 +367,16 @@ private fun ControlsBottom( @Composable private fun CaptureButton( - previewUiState: PreviewUiState.Ready, - isQuickSettingsOpen: Boolean, - videoRecordingState: VideoRecordingState, modifier: Modifier = Modifier, + captureButtonUiState: CaptureButtonUiState, + isQuickSettingsOpen: Boolean, + onToggleQuickSettings: () -> Unit = {}, onCaptureImageWithUri: ( ContentResolver, Uri?, Boolean, (PreviewViewModel.ImageCaptureEvent, Int) -> Unit ) -> Unit = { _, _, _, _ -> }, - onToggleQuickSettings: () -> Unit = {}, onStartVideoRecording: ( Uri?, Boolean, @@ -391,46 +389,49 @@ private fun CaptureButton( CaptureButton( modifier = modifier.testTag(CAPTURE_BUTTON), - onClick = { - multipleEventsCutter.processEvent { - when (previewUiState.previewMode) { - is PreviewMode.StandardMode -> { - onCaptureImageWithUri( - context.contentResolver, - null, - true - ) { event: PreviewViewModel.ImageCaptureEvent, _: Int -> - previewUiState.previewMode.onImageCapture(event) + onCaptureImage = { + if (captureButtonUiState is CaptureButtonUiState.Enabled) { + multipleEventsCutter.processEvent { + when (captureButtonUiState.previewMode) { + is PreviewMode.StandardMode -> { + onCaptureImageWithUri( + context.contentResolver, + null, + true + ) { event: PreviewViewModel.ImageCaptureEvent, _: Int -> + captureButtonUiState.previewMode.onImageCapture(event) + } } - } - is PreviewMode.ExternalImageCaptureMode -> { - onCaptureImageWithUri( - context.contentResolver, - previewUiState.previewMode.imageCaptureUri, - false - ) { event: PreviewViewModel.ImageCaptureEvent, _: Int -> - previewUiState.previewMode.onImageCapture(event) + is PreviewMode.ExternalImageCaptureMode -> { + onCaptureImageWithUri( + context.contentResolver, + captureButtonUiState.previewMode.imageCaptureUri, + false + ) { event: PreviewViewModel.ImageCaptureEvent, _: Int -> + captureButtonUiState.previewMode.onImageCapture(event) + } } - } - is PreviewMode.ExternalMultipleImageCaptureMode -> { - val ignoreUri = previewUiState.previewMode.imageCaptureUris.isNullOrEmpty() - onCaptureImageWithUri( - context.contentResolver, - null, - previewUiState.previewMode.imageCaptureUris.isNullOrEmpty() || - ignoreUri, - previewUiState.previewMode.onImageCapture - ) - } + is PreviewMode.ExternalMultipleImageCaptureMode -> { + val ignoreUri = + captureButtonUiState.previewMode.imageCaptureUris.isNullOrEmpty() + onCaptureImageWithUri( + context.contentResolver, + null, + captureButtonUiState.previewMode.imageCaptureUris.isNullOrEmpty() || + ignoreUri, + captureButtonUiState.previewMode.onImageCapture + ) + } - else -> { - onCaptureImageWithUri( - context.contentResolver, - null, - false - ) { _: PreviewViewModel.ImageCaptureEvent, _: Int -> } + else -> { + onCaptureImageWithUri( + context.contentResolver, + null, + false + ) { _: PreviewViewModel.ImageCaptureEvent, _: Int -> } + } } } } @@ -438,32 +439,34 @@ private fun CaptureButton( onToggleQuickSettings() } }, - onLongPress = { - when (previewUiState.previewMode) { - is PreviewMode.StandardMode -> { - onStartVideoRecording(null, false) {} - } + onStartVideoRecording = { + if (captureButtonUiState is CaptureButtonUiState.Enabled) { + when (captureButtonUiState.previewMode) { + is PreviewMode.StandardMode -> { + onStartVideoRecording(null, false) {} + } - is PreviewMode.ExternalVideoCaptureMode -> { - onStartVideoRecording( - previewUiState.previewMode.videoCaptureUri, - true, - previewUiState.previewMode.onVideoCapture - ) - } + is PreviewMode.ExternalVideoCaptureMode -> { + onStartVideoRecording( + captureButtonUiState.previewMode.videoCaptureUri, + true, + captureButtonUiState.previewMode.onVideoCapture + ) + } - else -> { - onStartVideoRecording(null, false) {} + else -> { + onStartVideoRecording(null, false) {} + } + } + if (isQuickSettingsOpen) { + onToggleQuickSettings() } - } - if (isQuickSettingsOpen) { - onToggleQuickSettings() } }, - onRelease = { + onStopVideoRecording = { onStopVideoRecording() }, - videoRecordingState = videoRecordingState + captureButtonUiState = captureButtonUiState ) } /* @@ -596,11 +599,12 @@ private fun Preview_ControlsBottom() { CompositionLocalProvider(LocalContentColor provides Color.White) { ControlsBottom( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, -// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Inactive() + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode {}, + VideoRecordingState.Inactive() + ) ), zoomLevel = 1.3f, showZoomLevel = true, @@ -617,11 +621,12 @@ private fun Preview_ControlsBottom_NoZoomLevel() { CompositionLocalProvider(LocalContentColor provides Color.White) { ControlsBottom( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, - // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Inactive() + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode({}), + VideoRecordingState.Inactive() + ) ), zoomLevel = 1.3f, showZoomLevel = false, @@ -638,11 +643,12 @@ private fun Preview_ControlsBottom_QuickSettingsOpen() { CompositionLocalProvider(LocalContentColor provides Color.White) { ControlsBottom( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, -// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Inactive() + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode {}, + VideoRecordingState.Inactive() + ) ), zoomLevel = 1.3f, showZoomLevel = true, @@ -659,11 +665,12 @@ private fun Preview_ControlsBottom_NoFlippableCamera() { CompositionLocalProvider(LocalContentColor provides Color.White) { ControlsBottom( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, -// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Inactive() + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode {}, + VideoRecordingState.Inactive() + ) ), zoomLevel = 1.3f, showZoomLevel = true, @@ -686,12 +693,13 @@ private fun Preview_ControlsBottom_Recording() { CompositionLocalProvider(LocalContentColor provides Color.White) { ControlsBottom( previewUiState = PreviewUiState.Ready( - currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, - previewMode = PreviewMode.StandardMode {}, -// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, - videoRecordingState = VideoRecordingState.Active.Recording(0L, .9, 1_000_000_000) - + videoRecordingState = VideoRecordingState.Active.Recording(0L, .9, 1_000_000_000), + captureButtonUiState = CaptureButtonUiState.Enabled( + CaptureMode.DEFAULT, + PreviewMode.StandardMode({}), + VideoRecordingState.Inactive() + ) ), zoomLevel = 1.3f, showZoomLevel = true, 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 index f1d3b5914..8e785fad1 100644 --- 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 @@ -106,6 +106,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.google.jetpackcamera.core.camera.VideoRecordingState +import com.google.jetpackcamera.feature.preview.CaptureButtonUiState import com.google.jetpackcamera.feature.preview.CaptureModeUiState import com.google.jetpackcamera.feature.preview.DisabledReason import com.google.jetpackcamera.feature.preview.PreviewUiState @@ -729,22 +730,34 @@ fun DropDownItem( @Composable fun CaptureButton( - onClick: () -> Unit, - onLongPress: () -> Unit, - onRelease: () -> Unit, - videoRecordingState: VideoRecordingState, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onCaptureImage: () -> Unit, + onStartVideoRecording: () -> Unit, + onStopVideoRecording: () -> Unit, + captureButtonUiState: CaptureButtonUiState ) { + var currentUiState = rememberUpdatedState(captureButtonUiState) var isPressedDown by remember { mutableStateOf(false) } + var isLongPressing by remember { + mutableStateOf(false) + } + val currentColor = LocalContentColor.current Box( modifier = modifier .pointerInput(Unit) { detectTapGestures( onLongPress = { - onLongPress() + isLongPressing = true + var curr = currentUiState.value + if (curr is CaptureButtonUiState.Enabled) { + when (curr.captureMode) { + CaptureMode.DEFAULT, CaptureMode.VIDEO_ONLY -> onStartVideoRecording() + CaptureMode.IMAGE_ONLY -> {} + } + } }, // TODO: @kimblebee - stopVideoRecording is being called every time the capture // button is pressed -- regardless of tap or long press @@ -752,9 +765,43 @@ fun CaptureButton( isPressedDown = true awaitRelease() isPressedDown = false - onRelease() + isLongPressing = false + var curr = currentUiState.value + when (curr) { + CaptureButtonUiState.Unavailable -> {} + is CaptureButtonUiState.Enabled -> { + if (curr.captureMode == CaptureMode.DEFAULT) { + onStopVideoRecording() + } + } + } }, - onTap = { onClick() } + onTap = { + var curr = currentUiState.value + when (curr) { + CaptureButtonUiState.Unavailable -> {} + is CaptureButtonUiState.Enabled -> { + Log.d(TAG, "capture mode ${curr.captureMode}") + if (!isLongPressing) { + when (curr.captureMode) { + CaptureMode.DEFAULT, + CaptureMode.IMAGE_ONLY -> onCaptureImage() + + CaptureMode.VIDEO_ONLY -> when (curr.videoRecordingState) { + is VideoRecordingState.Starting -> {} + is VideoRecordingState.Inactive -> { + onStartVideoRecording() + } + + is VideoRecordingState.Active -> { + onStopVideoRecording() + } + } + } + } + } + } + } ) } .size(120.dp) @@ -765,23 +812,31 @@ fun CaptureButton( modifier = Modifier .size(110.dp), onDraw = { - drawCircle( - alpha = when (videoRecordingState) { - is VideoRecordingState.Active.Paused -> .37f - else -> 1f - }, - color = - when (videoRecordingState) { - is VideoRecordingState.Inactive -> { - if (isPressedDown) currentColor else Color.Transparent - } + when (captureButtonUiState) { + CaptureButtonUiState.Unavailable -> { + drawCircle(color = Color.DarkGray) + } + + is CaptureButtonUiState.Enabled -> { + drawCircle( + alpha = when (captureButtonUiState.videoRecordingState) { + is VideoRecordingState.Active.Paused -> .37f + else -> 1f + }, + color = + when (captureButtonUiState.videoRecordingState) { + is VideoRecordingState.Inactive -> { + if (isPressedDown) currentColor else Color.Transparent + } - is VideoRecordingState.Active.Recording, - is VideoRecordingState.Active.Paused -> Color.Red + is VideoRecordingState.Active.Recording, + is VideoRecordingState.Active.Paused -> Color.Red - VideoRecordingState.Starting -> currentColor + VideoRecordingState.Starting -> currentColor + } + ) } - ) + } } ) } From a244284b098e10719d39bae238b19a70926d12d6 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Thu, 19 Dec 2024 14:24:42 +0000 Subject: [PATCH 2/3] do not allow video AND image use cases to be bound while HDR is active --- .../core/camera/CameraXCameraUseCase.kt | 8 +++-- .../feature/preview/CaptureModeUiState.kt | 5 +++ .../feature/preview/PreviewViewModel.kt | 31 +++++++++++++------ .../feature/preview/ui/TestTags.kt | 1 + .../preview/src/main/res/values/strings.xml | 2 ++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCase.kt index 2eaa652d6..b6a96ee9c 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCase.kt @@ -567,8 +567,12 @@ constructor( if (constraints.supportedImageFormatsMap[streamConfig] ?.contains(ImageOutputFormat.JPEG_ULTRA_HDR) == true ) { - // if both image/video are supported, we don't need to change our current capture mode - this.captureMode + // if both image/video are supported, only change capture mode if default is the current + if (this.captureMode != CaptureMode.DEFAULT) { + this.captureMode + } else { + CaptureMode.VIDEO_ONLY + } } else { // if only video is supported, change to video only CaptureMode.VIDEO_ONLY diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt index b00b68c05..c02e4984b 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt @@ -19,6 +19,7 @@ import com.google.jetpackcamera.feature.preview.ui.HDR_IMAGE_UNSUPPORTED_ON_DEVI import com.google.jetpackcamera.feature.preview.ui.HDR_IMAGE_UNSUPPORTED_ON_LENS_TAG import com.google.jetpackcamera.feature.preview.ui.HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG import com.google.jetpackcamera.feature.preview.ui.HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM_TAG +import com.google.jetpackcamera.feature.preview.ui.HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED_TAG import com.google.jetpackcamera.feature.preview.ui.HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG import com.google.jetpackcamera.feature.preview.ui.HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG @@ -73,6 +74,10 @@ enum class DisabledReason(val testTag: String, val reasonTextResId: Int) { HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM( HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG, R.string.toast_hdr_photo_unsupported_on_lens_multi_stream + ), + HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED( + HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED_TAG, + R.string.toast_hdr_simultaneous_image_video_unsupported ) } 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 238307852..a6e5f32af 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 @@ -382,6 +382,7 @@ class PreviewViewModel @AssistedInject constructor( } val supportedCaptureModes = getSupportedCaptureModes( cameraAppSettings, + isHdrOn, currentHdrDynamicRangeSupported, currentHdrImageFormatSupported ) @@ -417,7 +418,7 @@ class PreviewViewModel @AssistedInject constructor( videoCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) defaultCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) - } else { + } else if (!supportedCaptureModes.contains(CaptureMode.IMAGE_ONLY)) { val disabledReason = getCaptureModeDisabledReason( disabledCaptureMode = CaptureMode.IMAGE_ONLY, @@ -433,6 +434,13 @@ class PreviewViewModel @AssistedInject constructor( imageCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) defaultCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) + } else { + videoCaptureState = SingleSelectableState.Selectable + imageCaptureState = SingleSelectableState.Selectable + defaultCaptureState = + SingleSelectableState.Disabled( + disabledReason = DisabledReason.HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED + ) } return CaptureModeUiState.Enabled( currentSelection = cameraAppSettings.captureMode, @@ -445,6 +453,7 @@ class PreviewViewModel @AssistedInject constructor( private fun getSupportedCaptureModes( cameraAppSettings: CameraAppSettings, + isHdrOn: Boolean, currentHdrDynamicRangeSupported: Boolean, currentHdrImageFormatSupported: Boolean ): List = if ( @@ -454,7 +463,12 @@ class PreviewViewModel @AssistedInject constructor( currentHdrImageFormatSupported && cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF ) { - listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + // do not allow both use cases to be bound if hdr is on + if (isHdrOn) { + listOf(CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + } else { + listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + } } else if ( cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF && previewMode is PreviewMode.ExternalImageCaptureMode || @@ -540,14 +554,11 @@ class PreviewViewModel @AssistedInject constructor( fun getCaptureButtonUiState( cameraAppSettings: CameraAppSettings, cameraState: CameraState - ): CaptureButtonUiState { - Log.d(TAG, "new capture button state ${cameraAppSettings.captureMode}") - return CaptureButtonUiState.Enabled( - captureMode = cameraAppSettings.captureMode, - previewMode = previewMode, - videoRecordingState = cameraState.videoRecordingState - ) - } + ): CaptureButtonUiState = CaptureButtonUiState.Enabled( + captureMode = cameraAppSettings.captureMode, + previewMode = previewMode, + videoRecordingState = cameraState.videoRecordingState + ) /* private fun getCaptureToggleUiState( systemConstraints: SystemConstraints, diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/TestTags.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/TestTags.kt index 43a68db7e..0ac673e92 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/TestTags.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/TestTags.kt @@ -47,6 +47,7 @@ const val HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM_TAG = "HdrImageUnsupportedOnSin const val HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG = "HdrImageUnsupportedOnMultiStreamTag" const val HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG = "HdrVideoUnsupportedOnDeviceTag" const val HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG = "HdrVideoUnsupportedOnDeviceTag" +const val HDR_SIMULTANEOUS_IMAGE_VIDEO_UNSUPPORTED_TAG = "HdrSimultaneousImageVideoUnsupportedTag" const val ZOOM_RATIO_TAG = "ZoomRatioTag" const val LOGICAL_CAMERA_ID_TAG = "LogicalCameraIdTag" const val PHYSICAL_CAMERA_ID_TAG = "PhysicalCameraIdTag" diff --git a/feature/preview/src/main/res/values/strings.xml b/feature/preview/src/main/res/values/strings.xml index f899947fe..369a8b42e 100644 --- a/feature/preview/src/main/res/values/strings.xml +++ b/feature/preview/src/main/res/values/strings.xml @@ -47,6 +47,8 @@ Multi-stream mode does not support UltraHDR photo capture for current lens HDR video not supported on this device HDR video not supported by current lens + HDR video and image capture cannot be bound simultaneously + FRONT From 20e44ff87d83187ea89b14b4805cfd28312556a9 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Thu, 19 Dec 2024 14:25:58 +0000 Subject: [PATCH 3/3] comment to CaptureMode enums --- .../settings/model/CaptureMode.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt index 70fc0902a..b26051837 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt @@ -15,8 +15,35 @@ */ package com.google.jetpackcamera.settings.model +/** + * Class representing the app's configuration to capture an image + */ enum class CaptureMode { + + /** + * Both Image and Video use cases will be bound. + * + * Tap the Capture Button to take an image. + * + * Hold the Capture button to start recording, and release to complete the recording. + */ DEFAULT, + + /** + * Video use case will be bound. Image use case will not be bound. + * + * Tap the Capture Button to start recording. + * Hold the Capture button to start recording; releasing will not stop the recording. + * + * Tap the capture button again after recording has started to complete the recording. + */ VIDEO_ONLY, + + /** + * Image use case will be bound. Video use case will not be bound. + * + * Tap the Capture Button to capture an Image. + * Holding the Capture Button will do nothing. Subsequent release of the Capture button will also do nothing. + */ IMAGE_ONLY }