From c4c5233115aa1011eb0a3529e4256292923383f3 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 15:39:17 +0000 Subject: [PATCH 01/10] refactor usecaseMode references to CaptureMode --- .../core/camera/CameraSession.kt | 14 +++--- .../core/camera/CameraSessionSettings.kt | 7 ++- .../core/camera/CameraUseCase.kt | 6 +-- .../core/camera/CameraXCameraUseCase.kt | 29 ++++++------ .../core/camera/ConcurrentCameraSession.kt | 8 ++-- .../core/camera/test/FakeCameraUseCase.kt | 2 +- .../settings/model/CameraAppSettings.kt | 1 + .../settings/model/CaptureMode.kt | 23 +++++++++ .../feature/preview/PreviewViewModel.kt | 29 ++++++++---- .../preview/ui/PreviewScreenComponents.kt | 47 +++++++++++++++++++ 10 files changed, 127 insertions(+), 39 deletions(-) create mode 100644 data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt index bf0ed030a..1a1849577 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt @@ -61,6 +61,7 @@ import androidx.core.content.ContextCompat.checkSelfPermission import androidx.lifecycle.asFlow import com.google.jetpackcamera.core.camera.effects.SingleSurfaceForcingEffect import com.google.jetpackcamera.settings.model.AspectRatio +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange import com.google.jetpackcamera.settings.model.FlashMode @@ -96,7 +97,7 @@ private const val TAG = "CameraSession" context(CameraSessionContext) internal suspend fun runSingleCameraSession( sessionSettings: PerpetualSessionSettings.SingleCamera, - useCaseMode: CameraUseCase.UseCaseMode, + // useCaseMode: CameraUseCase.UseCaseMode, // TODO(tm): ImageCapture should go through an event channel like VideoCapture onImageCaptureCreated: (ImageCapture) -> Unit = {} ) = coroutineScope { @@ -105,8 +106,8 @@ internal suspend fun runSingleCameraSession( val initialCameraSelector = transientSettings.filterNotNull().first() .primaryLensFacing.toCameraSelector() - val videoCaptureUseCase = when (useCaseMode) { - CameraUseCase.UseCaseMode.STANDARD, CameraUseCase.UseCaseMode.VIDEO_ONLY -> + val videoCaptureUseCase = when (sessionSettings.captureMode) { + CaptureMode.DEFAULT, CaptureMode.VIDEO_ONLY -> createVideoUseCase( cameraProvider.getCameraInfo(initialCameraSelector), sessionSettings.aspectRatio, @@ -115,7 +116,6 @@ internal suspend fun runSingleCameraSession( sessionSettings.dynamicRange, backgroundDispatcher ) - else -> { null } @@ -144,7 +144,7 @@ internal suspend fun runSingleCameraSession( aspectRatio = sessionSettings.aspectRatio, dynamicRange = sessionSettings.dynamicRange, imageFormat = sessionSettings.imageFormat, - useCaseMode = useCaseMode, + captureMode = sessionSettings.captureMode, effect = when (sessionSettings.streamConfig) { StreamConfig.SINGLE_STREAM -> SingleSurfaceForcingEffect(this@coroutineScope) StreamConfig.MULTI_STREAM -> null @@ -292,7 +292,7 @@ internal fun createUseCaseGroup( videoCaptureUseCase: VideoCapture?, dynamicRange: DynamicRange, imageFormat: ImageOutputFormat, - useCaseMode: CameraUseCase.UseCaseMode, + captureMode: CaptureMode, effect: CameraEffect? = null ): UseCaseGroup { val previewUseCase = @@ -301,7 +301,7 @@ internal fun createUseCaseGroup( aspectRatio, stabilizationMode ) - val imageCaptureUseCase = if (useCaseMode != CameraUseCase.UseCaseMode.VIDEO_ONLY) { + val imageCaptureUseCase = if (captureMode != CaptureMode.VIDEO_ONLY) { createImageUseCase(cameraInfo, aspectRatio, dynamicRange, imageFormat) } else { null diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt index 7062ca753..28d3dc8a1 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt @@ -17,6 +17,7 @@ package com.google.jetpackcamera.core.camera import androidx.camera.core.CameraInfo import com.google.jetpackcamera.settings.model.AspectRatio +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange import com.google.jetpackcamera.settings.model.FlashMode @@ -33,9 +34,11 @@ import com.google.jetpackcamera.settings.model.StreamConfig */ internal sealed interface PerpetualSessionSettings { val aspectRatio: AspectRatio + val captureMode: CaptureMode data class SingleCamera( override val aspectRatio: AspectRatio, + override val captureMode: CaptureMode, val streamConfig: StreamConfig, val targetFrameRate: Int, val stabilizationMode: StabilizationMode, @@ -47,7 +50,9 @@ internal sealed interface PerpetualSessionSettings { val primaryCameraInfo: CameraInfo, val secondaryCameraInfo: CameraInfo, override val aspectRatio: AspectRatio - ) : PerpetualSessionSettings + ) : PerpetualSessionSettings { + override val captureMode: CaptureMode = CaptureMode.VIDEO_ONLY + } } /** diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt index 9efbb1168..ad8264340 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt @@ -43,7 +43,7 @@ interface CameraUseCase { */ suspend fun initialize( cameraAppSettings: CameraAppSettings, - useCaseMode: UseCaseMode, + // useCaseMode: UseCaseMode, isDebugMode: Boolean = false, cameraPropertiesJSONCallback: (result: String) -> Unit ) @@ -141,11 +141,11 @@ interface CameraUseCase { data class OnVideoRecordError(val error: Throwable) : OnVideoRecordEvent } - enum class UseCaseMode { + /*enum class UseCaseMode { STANDARD, IMAGE_ONLY, VIDEO_ONLY - } + }*/ } sealed interface VideoRecordingState { 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 9e7ea8b30..aa4d9142e 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 @@ -43,6 +43,7 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings import com.google.jetpackcamera.settings.model.CameraConstraints import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_15 import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_60 +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.ConcurrentCameraMode import com.google.jetpackcamera.settings.model.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange @@ -61,7 +62,6 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import javax.inject.Inject -import kotlin.properties.Delegates import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope @@ -100,7 +100,7 @@ constructor( private var imageCaptureUseCase: ImageCapture? = null private lateinit var systemConstraints: SystemConstraints - private var useCaseMode by Delegates.notNull() + // private var useCaseMode by Delegates.notNull() private val screenFlashEvents: Channel = Channel(capacity = Channel.UNLIMITED) @@ -120,11 +120,11 @@ constructor( override suspend fun initialize( cameraAppSettings: CameraAppSettings, - useCaseMode: CameraUseCase.UseCaseMode, + // useCaseMode: CameraUseCase.UseCaseMode, isDebugMode: Boolean, cameraPropertiesJSONCallback: (result: String) -> Unit ) { - this.useCaseMode = useCaseMode + // this.useCaseMode = useCaseMode cameraProvider = ProcessCameraProvider.awaitInstance(application) // updates values for available cameras @@ -245,7 +245,7 @@ constructor( currentSettings.value = cameraAppSettings .tryApplyDynamicRangeConstraints() - .tryApplyAspectRatioForExternalCapture(this.useCaseMode) + .tryApplyAspectRatioForExternalCapture(cameraAppSettings.captureMode) .tryApplyImageFormatConstraints() .tryApplyFrameRateConstraints() .tryApplyStabilizationConstraints() @@ -300,6 +300,7 @@ constructor( PerpetualSessionSettings.SingleCamera( aspectRatio = currentCameraSettings.aspectRatio, + captureMode = currentCameraSettings.captureMode, streamConfig = currentCameraSettings.streamConfig, targetFrameRate = currentCameraSettings.targetFrameRate, stabilizationMode = resolvedStabilizationMode, @@ -353,16 +354,15 @@ constructor( try { when (sessionSettings) { is PerpetualSessionSettings.SingleCamera -> runSingleCameraSession( - sessionSettings, - useCaseMode = useCaseMode + sessionSettings + // useCaseMode = useCaseMode ) { imageCapture -> imageCaptureUseCase = imageCapture } is PerpetualSessionSettings.ConcurrentCamera -> runConcurrentCameraSession( - sessionSettings, - useCaseMode = CameraUseCase.UseCaseMode.VIDEO_ONLY + sessionSettings ) } } finally { @@ -571,13 +571,12 @@ constructor( } ?: this private fun CameraAppSettings.tryApplyAspectRatioForExternalCapture( - useCaseMode: CameraUseCase.UseCaseMode - ): CameraAppSettings = when (useCaseMode) { - CameraUseCase.UseCaseMode.STANDARD -> this - CameraUseCase.UseCaseMode.IMAGE_ONLY -> + captureMode: CaptureMode + ): CameraAppSettings = when (captureMode) { + CaptureMode.DEFAULT -> this + CaptureMode.IMAGE_ONLY -> this.copy(aspectRatio = AspectRatio.THREE_FOUR) - - CameraUseCase.UseCaseMode.VIDEO_ONLY -> + CaptureMode.VIDEO_ONLY -> this.copy(aspectRatio = AspectRatio.NINE_SIXTEEN) } diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt index 0ae0bdb0d..f0b497fee 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt @@ -20,6 +20,7 @@ import android.util.Log import androidx.camera.core.CompositionSettings import androidx.camera.core.TorchState import androidx.lifecycle.asFlow +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.DynamicRange import com.google.jetpackcamera.settings.model.ImageOutputFormat import com.google.jetpackcamera.settings.model.StabilizationMode @@ -35,8 +36,7 @@ private const val TAG = "ConcurrentCameraSession" context(CameraSessionContext) @SuppressLint("RestrictedApi") internal suspend fun runConcurrentCameraSession( - sessionSettings: PerpetualSessionSettings.ConcurrentCamera, - useCaseMode: CameraUseCase.UseCaseMode + sessionSettings: PerpetualSessionSettings.ConcurrentCamera ) = coroutineScope { val primaryLensFacing = sessionSettings.primaryCameraInfo.appLensFacing val secondaryLensFacing = sessionSettings.secondaryCameraInfo.appLensFacing @@ -50,7 +50,7 @@ internal suspend fun runConcurrentCameraSession( .filterNotNull() .first() - val videoCapture = if (useCaseMode != CameraUseCase.UseCaseMode.IMAGE_ONLY) { + val videoCapture = if (sessionSettings.captureMode != CaptureMode.IMAGE_ONLY) { createVideoUseCase( cameraProvider.getCameraInfo( initialTransientSettings.primaryLensFacing.toCameraSelector() @@ -72,7 +72,7 @@ internal suspend fun runConcurrentCameraSession( aspectRatio = sessionSettings.aspectRatio, dynamicRange = DynamicRange.SDR, imageFormat = ImageOutputFormat.JPEG, - useCaseMode = useCaseMode, + captureMode = sessionSettings.captureMode, videoCaptureUseCase = videoCapture ) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt index c2b9198b2..b49fad5e8 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt @@ -62,7 +62,7 @@ class FakeCameraUseCase(defaultCameraSettings: CameraAppSettings = CameraAppSett override suspend fun initialize( cameraAppSettings: CameraAppSettings, - useCaseMode: CameraUseCase.UseCaseMode, + // useCaseMode: CameraUseCase.UseCaseMode, isDebugMode: Boolean, cameraPropertiesJSONCallback: (result: String) -> Unit ) { diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt index cff5044bc..5af96a458 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt @@ -21,6 +21,7 @@ const val UNLIMITED_VIDEO_DURATION = 0L * Data layer representation for settings. */ data class CameraAppSettings( + val captureMode: CaptureMode = CaptureMode.DEFAULT, val cameraLensFacing: LensFacing = LensFacing.BACK, val darkMode: DarkMode = DarkMode.SYSTEM, val flashMode: FlashMode = FlashMode.OFF, 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 new file mode 100644 index 000000000..54434aaf4 --- /dev/null +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CaptureMode.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.settings.model + +enum class CaptureMode { + DEFAULT, + VIDEO_ONLY, + IMAGE_ONLY +} 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 6d3015bf1..d3bf6c84c 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 @@ -39,6 +39,7 @@ import com.google.jetpackcamera.settings.SettingsRepository 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.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange @@ -112,12 +113,24 @@ class PreviewViewModel @AssistedInject constructor( // used to ensure we don't start the camera before initialization is complete. private var initializationDeferred: Deferred = viewModelScope.async { cameraUseCase.initialize( - cameraAppSettings = settingsRepository.defaultCameraAppSettings.first(), - previewMode.toUseCaseMode(), - isDebugMode + cameraAppSettings = settingsRepository.defaultCameraAppSettings.first() + .applyPreviewMode(previewMode), + isDebugMode = isDebugMode ) { cameraPropertiesJSON = it } } + /** + * updates the capture mode based on the preview mode + */ + private fun CameraAppSettings.applyPreviewMode(previewMode: PreviewMode): CameraAppSettings { + val captureMode = previewMode.toCaptureMode() + return if (captureMode == CaptureMode.DEFAULT) { + this + } else { + this.copy(captureMode = captureMode) + } + } + init { viewModelScope.launch { launch { @@ -270,11 +283,11 @@ class PreviewViewModel @AssistedInject constructor( } } - private fun PreviewMode.toUseCaseMode() = when (this) { - is PreviewMode.ExternalImageCaptureMode -> CameraUseCase.UseCaseMode.IMAGE_ONLY - is PreviewMode.ExternalMultipleImageCaptureMode -> CameraUseCase.UseCaseMode.IMAGE_ONLY - is PreviewMode.ExternalVideoCaptureMode -> CameraUseCase.UseCaseMode.VIDEO_ONLY - is PreviewMode.StandardMode -> CameraUseCase.UseCaseMode.STANDARD + private fun PreviewMode.toCaptureMode() = when (this) { + is PreviewMode.ExternalImageCaptureMode -> CaptureMode.IMAGE_ONLY + is PreviewMode.ExternalMultipleImageCaptureMode -> CaptureMode.IMAGE_ONLY + is PreviewMode.ExternalVideoCaptureMode -> CaptureMode.VIDEO_ONLY + is PreviewMode.StandardMode -> CaptureMode.DEFAULT } /** 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 3f154bb1d..c416df62d 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 @@ -622,6 +622,53 @@ fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) { } } +@Composable +private fun CaptureModeDropDown() { + var isExpanded by remember { mutableStateOf(false) } + var selectedOption by remember { mutableStateOf("Default") } + + Column { + Box( + modifier = Modifier + .clickable { isExpanded = !isExpanded } + .padding(8.dp) + ) { + Text(text = selectedOption, modifier = Modifier.padding(16.dp)) + } + AnimatedVisibility(visible = isExpanded) { + Column { + Text( + text = "Default", + modifier = Modifier + .clickable { + selectedOption = "Default" + isExpanded = false + } + .padding(16.dp) + ) + Text( + text = "Image Only", + modifier = Modifier + .clickable { + selectedOption = "Image Only" + isExpanded = false + } + .padding(16.dp) + ) + Text( + text = "Video Only", + modifier = Modifier + .clickable { + selectedOption = "Video Only" + isExpanded = false + } + .padding(16.dp) + ) + } + } + } +} + @Composable fun CaptureButton( onClick: () -> Unit, From 47f489687fa7f8d83e559c1874ea495882e84190 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 15:42:59 +0000 Subject: [PATCH 02/10] remove useCaseMode comments and other references --- .../jetpackcamera/core/camera/CameraXCameraUseCaseTest.kt | 2 +- .../com/google/jetpackcamera/core/camera/CameraSession.kt | 1 - .../com/google/jetpackcamera/core/camera/CameraUseCase.kt | 7 ------- .../jetpackcamera/core/camera/CameraXCameraUseCase.kt | 4 ---- .../jetpackcamera/core/camera/test/FakeCameraUseCase.kt | 1 - .../core/camera/test/FakeCameraUseCaseTest.kt | 6 ++---- .../jetpackcamera/feature/preview/ScreenFlashTest.kt | 3 +-- 7 files changed, 4 insertions(+), 20 deletions(-) diff --git a/core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCaseTest.kt b/core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCaseTest.kt index a8e0f3635..02335dee7 100644 --- a/core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCaseTest.kt +++ b/core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraUseCaseTest.kt @@ -168,7 +168,7 @@ class CameraXCameraUseCaseTest { iODispatcher = Dispatchers.IO, constraintsRepository = constraintsRepository ).apply { - initialize(appSettings, CameraUseCase.UseCaseMode.STANDARD) {} + initialize(appSettings) {} providePreviewSurface() } diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt index 1a1849577..7483b5cec 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt @@ -97,7 +97,6 @@ private const val TAG = "CameraSession" context(CameraSessionContext) internal suspend fun runSingleCameraSession( sessionSettings: PerpetualSessionSettings.SingleCamera, - // useCaseMode: CameraUseCase.UseCaseMode, // TODO(tm): ImageCapture should go through an event channel like VideoCapture onImageCaptureCreated: (ImageCapture) -> Unit = {} ) = coroutineScope { diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt index ad8264340..4be3ef377 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt @@ -43,7 +43,6 @@ interface CameraUseCase { */ suspend fun initialize( cameraAppSettings: CameraAppSettings, - // useCaseMode: UseCaseMode, isDebugMode: Boolean = false, cameraPropertiesJSONCallback: (result: String) -> Unit ) @@ -140,12 +139,6 @@ interface CameraUseCase { data class OnVideoRecordError(val error: Throwable) : OnVideoRecordEvent } - - /*enum class UseCaseMode { - STANDARD, - IMAGE_ONLY, - VIDEO_ONLY - }*/ } sealed interface VideoRecordingState { 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 aa4d9142e..a6e5056f0 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 @@ -100,7 +100,6 @@ constructor( private var imageCaptureUseCase: ImageCapture? = null private lateinit var systemConstraints: SystemConstraints - // private var useCaseMode by Delegates.notNull() private val screenFlashEvents: Channel = Channel(capacity = Channel.UNLIMITED) @@ -120,11 +119,9 @@ constructor( override suspend fun initialize( cameraAppSettings: CameraAppSettings, - // useCaseMode: CameraUseCase.UseCaseMode, isDebugMode: Boolean, cameraPropertiesJSONCallback: (result: String) -> Unit ) { - // this.useCaseMode = useCaseMode cameraProvider = ProcessCameraProvider.awaitInstance(application) // updates values for available cameras @@ -355,7 +352,6 @@ constructor( when (sessionSettings) { is PerpetualSessionSettings.SingleCamera -> runSingleCameraSession( sessionSettings - // useCaseMode = useCaseMode ) { imageCapture -> imageCaptureUseCase = imageCapture } diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt index b49fad5e8..a7f652619 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt @@ -62,7 +62,6 @@ class FakeCameraUseCase(defaultCameraSettings: CameraAppSettings = CameraAppSett override suspend fun initialize( cameraAppSettings: CameraAppSettings, - // useCaseMode: CameraUseCase.UseCaseMode, isDebugMode: Boolean, cameraPropertiesJSONCallback: (result: String) -> Unit ) { diff --git a/core/camera/src/test/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCaseTest.kt b/core/camera/src/test/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCaseTest.kt index 6ab07a8c3..09f9d2ffe 100644 --- a/core/camera/src/test/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCaseTest.kt +++ b/core/camera/src/test/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCaseTest.kt @@ -56,8 +56,7 @@ class FakeCameraUseCaseTest { @Test fun canInitialize() = runTest(testDispatcher) { cameraUseCase.initialize( - cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS, - useCaseMode = CameraUseCase.UseCaseMode.STANDARD + cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS ) {} } @@ -150,8 +149,7 @@ class FakeCameraUseCaseTest { private fun TestScope.initAndRunCamera() { backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { cameraUseCase.initialize( - cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS, - useCaseMode = CameraUseCase.UseCaseMode.STANDARD + cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS ) {} cameraUseCase.runCamera() } diff --git a/feature/preview/src/test/java/com/google/jetpackcamera/feature/preview/ScreenFlashTest.kt b/feature/preview/src/test/java/com/google/jetpackcamera/feature/preview/ScreenFlashTest.kt index 1601d1964..30c17f959 100644 --- a/feature/preview/src/test/java/com/google/jetpackcamera/feature/preview/ScreenFlashTest.kt +++ b/feature/preview/src/test/java/com/google/jetpackcamera/feature/preview/ScreenFlashTest.kt @@ -114,8 +114,7 @@ class ScreenFlashTest { private fun runCameraTest(testBody: suspend TestScope.() -> Unit) = runTest(testDispatcher) { backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { cameraUseCase.initialize( - DEFAULT_CAMERA_APP_SETTINGS, - CameraUseCase.UseCaseMode.STANDARD + DEFAULT_CAMERA_APP_SETTINGS ) {} cameraUseCase.runCamera() } From 72d77bc3368415382d3ef75eaa4db3235f17c312 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 15:57:47 +0000 Subject: [PATCH 03/10] Spotless --- .../java/com/google/jetpackcamera/settings/model/CaptureMode.kt | 1 - 1 file changed, 1 deletion(-) 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 54434aaf4..70fc0902a 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 @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.google.jetpackcamera.settings.model enum class CaptureMode { From 81a72216a3c772d65df9ef911c7b87d8261e7934 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 12:18:11 -0500 Subject: [PATCH 04/10] Update PreviewScreenComponents.kt remove a change intended for another branch --- .../preview/ui/PreviewScreenComponents.kt | 47 ------------------- 1 file changed, 47 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 index c416df62d..3f154bb1d 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 @@ -622,53 +622,6 @@ fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) { } } -@Composable -private fun CaptureModeDropDown() { - var isExpanded by remember { mutableStateOf(false) } - var selectedOption by remember { mutableStateOf("Default") } - - Column { - Box( - modifier = Modifier - .clickable { isExpanded = !isExpanded } - .padding(8.dp) - ) { - Text(text = selectedOption, modifier = Modifier.padding(16.dp)) - } - AnimatedVisibility(visible = isExpanded) { - Column { - Text( - text = "Default", - modifier = Modifier - .clickable { - selectedOption = "Default" - isExpanded = false - } - .padding(16.dp) - ) - Text( - text = "Image Only", - modifier = Modifier - .clickable { - selectedOption = "Image Only" - isExpanded = false - } - .padding(16.dp) - ) - Text( - text = "Video Only", - modifier = Modifier - .clickable { - selectedOption = "Video Only" - isExpanded = false - } - .padding(16.dp) - ) - } - } - } -} - @Composable fun CaptureButton( onClick: () -> Unit, From 694bf7cac4aad043a420a4a749ec8d27b9074ca8 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 17:14:48 +0000 Subject: [PATCH 05/10] capture mode uistate --- .../preview/CaptureModeToggleUiState.kt | 23 ++- .../feature/preview/PreviewViewModel.kt | 172 ++++++++++++++++++ 2 files changed, 191 insertions(+), 4 deletions(-) diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt index 04b7a5e43..b778a414a 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt @@ -24,6 +24,23 @@ import com.google.jetpackcamera.feature.preview.ui.HDR_VIDEO_UNSUPPORTED_ON_LENS import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG +import com.google.jetpackcamera.settings.model.CaptureMode + +sealed interface CaptureModeUiState { + data class Enabled( + val currentSelection: CaptureMode, + val defaultCaptureState: SingleSelectableState = SingleSelectableState.Selectable, + val videoOnlyCaptureState: SingleSelectableState = SingleSelectableState.Selectable, + val imageOnlyCaptureState: SingleSelectableState = SingleSelectableState.Selectable + ) : CaptureModeUiState +} + +/** State for the individual options on Popup dialog settings */ +sealed interface SingleSelectableState { + data object Selectable : SingleSelectableState + data class Disabled(val disabledReason: CaptureModeToggleUiState.DisabledReason) : + SingleSelectableState +} sealed interface CaptureModeToggleUiState { @@ -35,10 +52,8 @@ sealed interface CaptureModeToggleUiState { data class Enabled(override val currentMode: ToggleMode) : Visible - data class Disabled( - override val currentMode: ToggleMode, - val disabledReason: DisabledReason - ) : Visible + data class Disabled(override val currentMode: ToggleMode, val disabledReason: DisabledReason) : + Visible enum class DisabledReason(val testTag: String, val reasonTextResId: Int) { VIDEO_CAPTURE_EXTERNAL_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 d3bf6c84c..fb1a2863f 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 @@ -346,6 +346,178 @@ class PreviewViewModel @AssistedInject constructor( } } + private fun getCaptureModeUiState( + systemConstraints: SystemConstraints, + cameraAppSettings: CameraAppSettings + ): CaptureModeUiState { + val cameraConstraints: CameraConstraints? = systemConstraints.forCurrentLens( + cameraAppSettings + ) + val hdrDynamicRangeSupported = cameraConstraints?.let { + it.supportedDynamicRanges.size > 1 + } == true + val hdrImageFormatSupported = + cameraConstraints?.supportedImageFormatsMap?.get(cameraAppSettings.streamConfig)?.let { + it.size > 1 + } == true + + val supportedCaptureModes = getSupportedCaptureModes( + cameraAppSettings, + hdrDynamicRangeSupported, + hdrImageFormatSupported + ) + // if all capture modes are supported, return capturemodeuistate + if (supportedCaptureModes.containsAll( + listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + ) + ) { + return CaptureModeUiState.Enabled(currentSelection = cameraAppSettings.captureMode) + } + // if all capture modes are not supported, give disabledReason + // if image or video is not supported, default will also be disabled + else { + lateinit var defaultCaptureState: SingleSelectableState.Disabled + lateinit var imageCaptureState: SingleSelectableState + lateinit var videoCaptureState: SingleSelectableState + if (!supportedCaptureModes.contains( + CaptureMode.VIDEO_ONLY + ) + ) { + val disabledReason = + getCaptureModeDisabledReason( + disabledCaptureMode = CaptureMode.VIDEO_ONLY, + hdrDynamicRangeSupported = hdrDynamicRangeSupported, + hdrImageFormatSupported = hdrImageFormatSupported, + systemConstraints = systemConstraints, + cameraAppSettings.cameraLensFacing, + cameraAppSettings.streamConfig, + cameraAppSettings.concurrentCameraMode + ) + + imageCaptureState = SingleSelectableState.Selectable + videoCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) + defaultCaptureState = + SingleSelectableState.Disabled(disabledReason = disabledReason) + } else { + val disabledReason = + getCaptureModeDisabledReason( + disabledCaptureMode = CaptureMode.IMAGE_ONLY, + hdrDynamicRangeSupported, + hdrImageFormatSupported, + systemConstraints, + cameraAppSettings.cameraLensFacing, + cameraAppSettings.streamConfig, + cameraAppSettings.concurrentCameraMode + ) + + videoCaptureState = SingleSelectableState.Selectable + imageCaptureState = SingleSelectableState.Disabled(disabledReason = disabledReason) + defaultCaptureState = + SingleSelectableState.Disabled(disabledReason = disabledReason) + } + return CaptureModeUiState.Enabled( + currentSelection = CaptureMode.DEFAULT, + videoOnlyCaptureState = videoCaptureState, + imageOnlyCaptureState = imageCaptureState, + defaultCaptureState = defaultCaptureState + ) + } + } + + private fun getSupportedCaptureModes( + cameraAppSettings: CameraAppSettings, + hdrDynamicRangeSupported: Boolean, + hdrImageFormatSupported: Boolean + ): List = if ( + previewMode !is PreviewMode.ExternalImageCaptureMode && + previewMode !is PreviewMode.ExternalVideoCaptureMode && + hdrDynamicRangeSupported && + hdrImageFormatSupported && + cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF + ) { + listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + } else if ( + cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF && + previewMode is PreviewMode.ExternalImageCaptureMode || + cameraAppSettings.imageFormat == ImageOutputFormat.JPEG_ULTRA_HDR + ) { + listOf(CaptureMode.IMAGE_ONLY) + } else { + listOf(CaptureMode.VIDEO_ONLY) + } + + private fun getCaptureModeDisabledReason( + disabledCaptureMode: CaptureMode, + hdrDynamicRangeSupported: Boolean, + hdrImageFormatSupported: Boolean, + systemConstraints: SystemConstraints, + currentLensFacing: LensFacing, + currentStreamConfig: StreamConfig, + concurrentCameraMode: ConcurrentCameraMode + ): CaptureModeToggleUiState.DisabledReason { + when (disabledCaptureMode) { + CaptureMode.VIDEO_ONLY -> { + if (previewMode is PreviewMode.ExternalVideoCaptureMode) { + return CaptureModeToggleUiState.DisabledReason + .IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED + } + + if (concurrentCameraMode == ConcurrentCameraMode.DUAL) { + return CaptureModeToggleUiState.DisabledReason + .IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA + } + + if (!hdrImageFormatSupported) { + // First check if Ultra HDR image is supported on other capture modes + if (systemConstraints + .perLensConstraints[currentLensFacing] + ?.supportedImageFormatsMap + ?.anySupportsUltraHdr { it != currentStreamConfig } == true + ) { + return when (currentStreamConfig) { + StreamConfig.MULTI_STREAM -> + CaptureModeToggleUiState.DisabledReason + .HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM + + StreamConfig.SINGLE_STREAM -> + CaptureModeToggleUiState.DisabledReason + .HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM + } + } + + // Check if any other lens supports HDR image + if (systemConstraints.anySupportsUltraHdr { it != currentLensFacing }) { + return CaptureModeToggleUiState.DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_LENS + } + + // No lenses support HDR image on device + return CaptureModeToggleUiState.DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_DEVICE + } + + throw RuntimeException("Unknown DisabledReason for video mode.") + } + + CaptureMode.IMAGE_ONLY -> { + if (previewMode is PreviewMode.ExternalImageCaptureMode) { + return CaptureModeToggleUiState.DisabledReason + .VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED + } + + if (!hdrDynamicRangeSupported) { + if (systemConstraints.anySupportsHdrDynamicRange { it != currentLensFacing }) { + return CaptureModeToggleUiState.DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_LENS + } + return CaptureModeToggleUiState.DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_DEVICE + } + + throw RuntimeException("Unknown DisabledReason for image mode.") + } + CaptureMode.DEFAULT -> { + TODO() + } + } + } + private fun getCaptureToggleUiState( systemConstraints: SystemConstraints, cameraAppSettings: CameraAppSettings From 6d8d009d96ecc2b3c9ef5004defc7afbb8286a61 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Tue, 17 Dec 2024 18:25:52 +0000 Subject: [PATCH 06/10] WIP capture mode UI component --- .../preview/ui/CameraControlsOverlay.kt | 13 ++++ .../preview/ui/PreviewScreenComponents.kt | 60 +++++++++++++++++++ 2 files changed, 73 insertions(+) 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 7efa1ec1c..246d10541 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 @@ -268,6 +268,19 @@ private fun ControlsBottom( ) -> Unit = { _, _, _ -> }, onStopVideoRecording: () -> Unit = {} ) { + if (videoRecordingState is VideoRecordingState.Inactive) { + Box(modifier = Modifier.fillMaxSize()) { + // todo(kc): WIP UI properly style this + CaptureModeDropDown( + modifier = Modifier.align( + Alignment.BottomEnd + ), + onSelectCaptureMode = { + TODO() + } + ) + } + } Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { CompositionLocalProvider( LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 20.sp) 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 3f154bb1d..6458f401e 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 @@ -30,8 +30,10 @@ import androidx.compose.animation.core.EaseOutExpo import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -109,6 +111,7 @@ import com.google.jetpackcamera.feature.preview.R import com.google.jetpackcamera.feature.preview.StabilizationUiState import com.google.jetpackcamera.feature.preview.ui.theme.PreviewPreviewTheme import com.google.jetpackcamera.settings.model.AspectRatio +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.StabilizationMode import kotlin.time.Duration.Companion.nanoseconds import kotlinx.coroutines.delay @@ -622,6 +625,63 @@ fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) { } } +@Composable +fun CaptureModeDropDown( + modifier: Modifier = Modifier, + onSelectCaptureMode: (CaptureMode) -> Unit + // +// captureModeUiState: CaptureModeUiState +) { + var isExpanded by remember { mutableStateOf(false) } + var selectedOption by remember { mutableStateOf("Default") } + + Column(modifier = modifier) { + AnimatedVisibility( + visible = isExpanded, + enter = + fadeIn() + expandVertically(expandFrom = Alignment.Top), + exit = shrinkVertically(shrinkTowards = Alignment.Bottom) + ) { + Column { + Text( + text = "Default", + modifier = Modifier + .clickable { + selectedOption = "Default" + isExpanded = false + } + .padding(16.dp) + ) + Text( + text = "Image Only", + modifier = Modifier + .clickable { + selectedOption = "Image Only" + isExpanded = false + } + .padding(16.dp) + ) + Text( + text = "Video Only", + modifier = Modifier + .clickable { + selectedOption = "Video Only" + isExpanded = false + } + .padding(16.dp) + ) + } + } + Box( + modifier = Modifier + .clickable { isExpanded = !isExpanded } + .padding(8.dp) + ) { + Text(text = selectedOption, modifier = Modifier.padding(16.dp)) + } + } +} + @Composable fun CaptureButton( onClick: () -> Unit, From a27fa3ee87d8e802fc5f8d58020a201912cee062 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Wed, 18 Dec 2024 00:47:46 +0000 Subject: [PATCH 07/10] new UI for capture mode --- .../core/camera/CameraUseCase.kt | 2 + .../core/camera/CameraXCameraUseCase.kt | 46 +++++++++ .../core/camera/test/FakeCameraUseCase.kt | 7 ++ .../preview/CaptureModeToggleUiState.kt | 1 + .../feature/preview/PreviewScreen.kt | 4 + .../feature/preview/PreviewUiState.kt | 7 +- .../feature/preview/PreviewViewModel.kt | 67 +++++++++---- .../preview/ui/CameraControlsOverlay.kt | 21 ++-- .../preview/ui/PreviewScreenComponents.kt | 98 ++++++++++++++----- 9 files changed, 194 insertions(+), 59 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt index 4be3ef377..7d8508542 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraUseCase.kt @@ -21,6 +21,7 @@ import androidx.camera.core.ImageCapture import androidx.camera.core.SurfaceRequest import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CameraAppSettings +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.ConcurrentCameraMode import com.google.jetpackcamera.settings.model.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange @@ -119,6 +120,7 @@ interface CameraUseCase { suspend fun setTargetFrameRate(targetFrameRate: Int) suspend fun setMaxVideoDuration(durationInMillis: Long) + suspend fun setCaptureMode(captureMode: CaptureMode) /** * Represents the events required for screen flash. 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 a6e5056f0..2eaa652d6 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 @@ -248,6 +248,7 @@ constructor( .tryApplyStabilizationConstraints() .tryApplyConcurrentCameraModeConstraints() .tryApplyFlashModeConstraints() + .tryApplyCaptureModeConstraints() if (isDebugMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { withContext(iODispatcher) { val cameraPropertiesJSON = @@ -545,12 +546,49 @@ constructor( ?.tryApplyDynamicRangeConstraints() ?.tryApplyImageFormatConstraints() ?.tryApplyFlashModeConstraints() + ?.tryApplyCaptureModeConstraints() } else { old } } } + private fun CameraAppSettings.tryApplyCaptureModeConstraints(): CameraAppSettings { + Log.d(TAG, "applying capture mode constraints") + systemConstraints.perLensConstraints[cameraLensFacing]?.let { constraints -> + val newCaptureMode = + if (concurrentCameraMode == ConcurrentCameraMode.DUAL) { + Log.d(TAG, "CONCURRENT CAMERA CAPTURE MODE") + CaptureMode.VIDEO_ONLY + } else if (dynamicRange == DynamicRange.HLG10 || + imageFormat == ImageOutputFormat.JPEG_ULTRA_HDR + ) { + if (constraints.supportedDynamicRanges.contains(DynamicRange.HLG10)) { + 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 + } else { + // if only video is supported, change to video only + CaptureMode.VIDEO_ONLY + } + } else { + // if only image is supported, change to image only + CaptureMode.IMAGE_ONLY + } + } else { + // if no dynamic range value is set, its OK to return the current value + return this + } + Log.d(TAG, "new capture mode $newCaptureMode") + return this@tryApplyCaptureModeConstraints.copy( + captureMode = newCaptureMode + ) + } + ?: return this + } + private fun CameraAppSettings.tryApplyDynamicRangeConstraints(): CameraAppSettings = systemConstraints.perLensConstraints[cameraLensFacing]?.let { constraints -> with(constraints.supportedDynamicRanges) { @@ -682,6 +720,7 @@ constructor( old?.copy(streamConfig = streamConfig) ?.tryApplyImageFormatConstraints() ?.tryApplyConcurrentCameraModeConstraints() + ?.tryApplyCaptureModeConstraints() } } @@ -689,6 +728,7 @@ constructor( currentSettings.update { old -> old?.copy(dynamicRange = dynamicRange) ?.tryApplyConcurrentCameraModeConstraints() + ?.tryApplyCaptureModeConstraints() } } @@ -702,12 +742,14 @@ constructor( currentSettings.update { old -> old?.copy(concurrentCameraMode = concurrentCameraMode) ?.tryApplyConcurrentCameraModeConstraints() + ?.tryApplyCaptureModeConstraints() } } override suspend fun setImageFormat(imageFormat: ImageOutputFormat) { currentSettings.update { old -> old?.copy(imageFormat = imageFormat) + ?.tryApplyCaptureModeConstraints() } } @@ -719,6 +761,10 @@ constructor( } } + override suspend fun setCaptureMode(captureMode: CaptureMode) { + currentSettings.update { old -> old?.copy(captureMode = captureMode) } + } + override suspend fun setStabilizationMode(stabilizationMode: StabilizationMode) { currentSettings.update { old -> old?.copy(stabilizationMode = stabilizationMode) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt index a7f652619..07d7fb1ec 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/test/FakeCameraUseCase.kt @@ -24,6 +24,7 @@ import com.google.jetpackcamera.core.camera.CameraState import com.google.jetpackcamera.core.camera.CameraUseCase import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CameraAppSettings +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.ConcurrentCameraMode import com.google.jetpackcamera.settings.model.DeviceRotation import com.google.jetpackcamera.settings.model.DynamicRange @@ -244,4 +245,10 @@ class FakeCameraUseCase(defaultCameraSettings: CameraAppSettings = CameraAppSett old.copy(maxVideoDurationMillis = durationInMillis) } } + + override suspend fun setCaptureMode(captureMode: CaptureMode) { + currentSettings.update { old -> + old.copy(captureMode = captureMode) + } + } } diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt index b778a414a..447a5b2d6 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt @@ -27,6 +27,7 @@ import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_EXTERNAL_UNSUPP import com.google.jetpackcamera.settings.model.CaptureMode sealed interface CaptureModeUiState { + data object Unavailable : CaptureModeUiState data class Enabled( val currentSelection: CaptureMode, val defaultCaptureState: SingleSelectableState = SingleSelectableState.Selectable, 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 c4d5f41f1..37b6d2519 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 @@ -62,6 +62,7 @@ import com.google.jetpackcamera.feature.preview.ui.ZoomLevelDisplayState import com.google.jetpackcamera.feature.preview.ui.debouncedOrientationFlow import com.google.jetpackcamera.feature.preview.ui.debug.DebugOverlayComponent import com.google.jetpackcamera.settings.model.AspectRatio +import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.ConcurrentCameraMode import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS import com.google.jetpackcamera.settings.model.DynamicRange @@ -141,6 +142,7 @@ fun PreviewScreen( onSetLensFacing = viewModel::setLensFacing, onTapToFocus = viewModel::tapToFocus, onChangeZoomScale = viewModel::setZoomScale, + onSetCaptureMode = viewModel::setCaptureMode, onChangeFlash = viewModel::setFlash, onChangeAspectRatio = viewModel::setAspectRatio, onChangeCaptureMode = viewModel::setCaptureMode, @@ -173,6 +175,7 @@ private fun ContentScreen( modifier: Modifier = Modifier, onNavigateToSettings: () -> Unit = {}, onClearUiScreenBrightness: (Float) -> Unit = {}, + onSetCaptureMode: (CaptureMode) -> Unit = {}, onSetLensFacing: (newLensFacing: LensFacing) -> Unit = {}, onTapToFocus: (x: Float, y: Float) -> Unit = { _, _ -> }, onChangeZoomScale: (Float) -> Unit = {}, @@ -253,6 +256,7 @@ private fun ContentScreen( CameraControlsOverlay( previewUiState = previewUiState, onNavigateToSettings = onNavigateToSettings, + onSetCaptureMode = onSetCaptureMode, onFlipCamera = onFlipCamera, onChangeFlash = onChangeFlash, onMuteAudio = onToggleMuteAudio, 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 53f9f0dab..e8381994c 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 @@ -49,7 +49,8 @@ sealed interface PreviewUiState { val currentLogicalCameraId: String? = null, val debugUiState: DebugUiState = DebugUiState(), val stabilizationUiState: StabilizationUiState = StabilizationUiState.Disabled, - val flashModeUiState: FlashModeUiState = FlashModeUiState.Unavailable + val flashModeUiState: FlashModeUiState = FlashModeUiState.Unavailable, + val captureModeUiState: CaptureModeUiState = CaptureModeUiState.Unavailable ) : PreviewUiState } @@ -80,9 +81,7 @@ sealed interface StabilizationUiState { } } - data class Auto( - override val stabilizationMode: StabilizationMode - ) : Enabled { + data class Auto(override val stabilizationMode: StabilizationMode) : Enabled { override val active = true } } 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 fb1a2863f..6f6b8e5b2 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 @@ -204,7 +204,11 @@ class PreviewViewModel @AssistedInject constructor( cameraAppSettings, cameraState ), - flashModeUiState = flashModeUiState + flashModeUiState = flashModeUiState, + captureModeUiState = getCaptureModeUiState( + systemConstraints, + cameraAppSettings + ) // TODO(kc): set elapsed time UI state once VideoRecordingState // refactor is complete. ) @@ -350,22 +354,37 @@ class PreviewViewModel @AssistedInject constructor( systemConstraints: SystemConstraints, cameraAppSettings: CameraAppSettings ): CaptureModeUiState { + Log.d(TAG, "CREATING CAPMODE UISTATE") val cameraConstraints: CameraConstraints? = systemConstraints.forCurrentLens( cameraAppSettings ) - val hdrDynamicRangeSupported = cameraConstraints?.let { - it.supportedDynamicRanges.size > 1 - } == true - val hdrImageFormatSupported = - cameraConstraints?.supportedImageFormatsMap?.get(cameraAppSettings.streamConfig)?.let { - it.size > 1 - } == true + val isHdrOn = cameraAppSettings.dynamicRange == DynamicRange.HLG10 || + cameraAppSettings.imageFormat == ImageOutputFormat.JPEG_ULTRA_HDR + val currentHdrDynamicRangeSupported = + if (isHdrOn) { + cameraConstraints?.supportedDynamicRanges?.contains(DynamicRange.HLG10) == true + } else { + true + } + + val currentHdrImageFormatSupported = + if (isHdrOn) { + cameraConstraints?.supportedImageFormatsMap?.get( + cameraAppSettings.streamConfig + )?.contains(ImageOutputFormat.JPEG_ULTRA_HDR) == true + } else { + true + } + Log.d(TAG, "current hdr video supported? $currentHdrDynamicRangeSupported") + + Log.d(TAG, "current hdr image supported? $currentHdrImageFormatSupported") val supportedCaptureModes = getSupportedCaptureModes( cameraAppSettings, - hdrDynamicRangeSupported, - hdrImageFormatSupported + currentHdrDynamicRangeSupported, + currentHdrImageFormatSupported ) + Log.d(TAG, "supported capture modes? $supportedCaptureModes") // if all capture modes are supported, return capturemodeuistate if (supportedCaptureModes.containsAll( listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) @@ -386,8 +405,8 @@ class PreviewViewModel @AssistedInject constructor( val disabledReason = getCaptureModeDisabledReason( disabledCaptureMode = CaptureMode.VIDEO_ONLY, - hdrDynamicRangeSupported = hdrDynamicRangeSupported, - hdrImageFormatSupported = hdrImageFormatSupported, + hdrDynamicRangeSupported = currentHdrDynamicRangeSupported, + hdrImageFormatSupported = currentHdrImageFormatSupported, systemConstraints = systemConstraints, cameraAppSettings.cameraLensFacing, cameraAppSettings.streamConfig, @@ -402,8 +421,8 @@ class PreviewViewModel @AssistedInject constructor( val disabledReason = getCaptureModeDisabledReason( disabledCaptureMode = CaptureMode.IMAGE_ONLY, - hdrDynamicRangeSupported, - hdrImageFormatSupported, + currentHdrDynamicRangeSupported, + currentHdrImageFormatSupported, systemConstraints, cameraAppSettings.cameraLensFacing, cameraAppSettings.streamConfig, @@ -416,7 +435,7 @@ class PreviewViewModel @AssistedInject constructor( SingleSelectableState.Disabled(disabledReason = disabledReason) } return CaptureModeUiState.Enabled( - currentSelection = CaptureMode.DEFAULT, + currentSelection = cameraAppSettings.captureMode, videoOnlyCaptureState = videoCaptureState, imageOnlyCaptureState = imageCaptureState, defaultCaptureState = defaultCaptureState @@ -426,13 +445,13 @@ class PreviewViewModel @AssistedInject constructor( private fun getSupportedCaptureModes( cameraAppSettings: CameraAppSettings, - hdrDynamicRangeSupported: Boolean, - hdrImageFormatSupported: Boolean + currentHdrDynamicRangeSupported: Boolean, + currentHdrImageFormatSupported: Boolean ): List = if ( previewMode !is PreviewMode.ExternalImageCaptureMode && previewMode !is PreviewMode.ExternalVideoCaptureMode && - hdrDynamicRangeSupported && - hdrImageFormatSupported && + currentHdrDynamicRangeSupported && + currentHdrImageFormatSupported && cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF ) { listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) @@ -456,7 +475,7 @@ class PreviewViewModel @AssistedInject constructor( concurrentCameraMode: ConcurrentCameraMode ): CaptureModeToggleUiState.DisabledReason { when (disabledCaptureMode) { - CaptureMode.VIDEO_ONLY -> { + CaptureMode.IMAGE_ONLY -> { if (previewMode is PreviewMode.ExternalVideoCaptureMode) { return CaptureModeToggleUiState.DisabledReason .IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED @@ -497,7 +516,7 @@ class PreviewViewModel @AssistedInject constructor( throw RuntimeException("Unknown DisabledReason for video mode.") } - CaptureMode.IMAGE_ONLY -> { + CaptureMode.VIDEO_ONLY -> { if (previewMode is PreviewMode.ExternalImageCaptureMode) { return CaptureModeToggleUiState.DisabledReason .VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED @@ -1001,6 +1020,12 @@ class PreviewViewModel @AssistedInject constructor( } } + fun setCaptureMode(captureMode: CaptureMode) { + viewModelScope.launch { + cameraUseCase.setCaptureMode(captureMode) + } + } + // modify ui values fun toggleQuickSettings() { viewModelScope.launch { 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 246d10541..b6f87e205 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 @@ -56,6 +56,7 @@ 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.CaptureModeToggleUiState +import com.google.jetpackcamera.feature.preview.CaptureModeUiState import com.google.jetpackcamera.feature.preview.FlashModeUiState import com.google.jetpackcamera.feature.preview.MultipleEventsCutter import com.google.jetpackcamera.feature.preview.PreviewMode @@ -67,6 +68,7 @@ import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsIn 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 import com.google.jetpackcamera.settings.model.LensFacing @@ -94,6 +96,7 @@ fun CameraControlsOverlay( modifier: Modifier = Modifier, zoomLevelDisplayState: ZoomLevelDisplayState = remember { ZoomLevelDisplayState() }, onNavigateToSettings: () -> Unit = {}, + onSetCaptureMode: (CaptureMode) -> Unit = {}, onFlipCamera: () -> Unit = {}, onChangeFlash: (FlashMode) -> Unit = {}, onChangeImageFormat: (ImageOutputFormat) -> Unit = {}, @@ -159,6 +162,7 @@ fun CameraControlsOverlay( isQuickSettingsOpen = previewUiState.quickSettingsIsOpen, systemConstraints = previewUiState.systemConstraints, videoRecordingState = previewUiState.videoRecordingState, + onSetCaptureMode = onSetCaptureMode, onFlipCamera = onFlipCamera, onCaptureImageWithUri = onCaptureImageWithUri, onToggleQuickSettings = onToggleQuickSettings, @@ -260,6 +264,7 @@ private fun ControlsBottom( onToggleAudioMuted: () -> Unit = {}, onSetPause: (Boolean) -> Unit = {}, onChangeImageFormat: (ImageOutputFormat) -> Unit = {}, + onSetCaptureMode: (CaptureMode) -> Unit = {}, onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {}, onStartVideoRecording: ( Uri?, @@ -268,16 +273,16 @@ private fun ControlsBottom( ) -> Unit = { _, _, _ -> }, onStopVideoRecording: () -> Unit = {} ) { - if (videoRecordingState is VideoRecordingState.Inactive) { + if (videoRecordingState is VideoRecordingState.Inactive && + previewUiState.captureModeUiState is CaptureModeUiState.Enabled + ) { Box(modifier = Modifier.fillMaxSize()) { - // todo(kc): WIP UI properly style this + // todo(kc): WIP UI... still need to properly style this CaptureModeDropDown( - modifier = Modifier.align( - Alignment.BottomEnd - ), - onSelectCaptureMode = { - TODO() - } + modifier = Modifier.align(Alignment.BottomEnd), + onSetCaptureMode = onSetCaptureMode, + captureModeUiState = previewUiState.captureModeUiState, + onDisabledCaptureMode = onToggleWhenDisabled ) } } 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 6458f401e..071f1798e 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,8 +106,11 @@ 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.CaptureModeToggleUiState +import com.google.jetpackcamera.feature.preview.CaptureModeUiState import com.google.jetpackcamera.feature.preview.PreviewUiState import com.google.jetpackcamera.feature.preview.R +import com.google.jetpackcamera.feature.preview.SingleSelectableState import com.google.jetpackcamera.feature.preview.StabilizationUiState import com.google.jetpackcamera.feature.preview.ui.theme.PreviewPreviewTheme import com.google.jetpackcamera.settings.model.AspectRatio @@ -491,6 +494,7 @@ fun StabilizationIcon( throw IllegalStateException( "AUTO is not a specific StabilizationUiState." ) + StabilizationMode.HIGH_QUALITY -> painterResource(R.drawable.video_stable_hq_filled_icon) @@ -506,6 +510,7 @@ fun StabilizationIcon( "${stabilizationUiState.stabilizationMode}" ) } + is StabilizationUiState.Auto -> { when (stabilizationUiState.stabilizationMode) { StabilizationMode.ON -> @@ -513,6 +518,7 @@ fun StabilizationIcon( StabilizationMode.OPTICAL -> painterResource(R.drawable.video_stable_ois_auto_filled_icon) + else -> TODO( "Auto stabilization not yet implemented for " + @@ -628,12 +634,11 @@ fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) { @Composable fun CaptureModeDropDown( modifier: Modifier = Modifier, - onSelectCaptureMode: (CaptureMode) -> Unit - // -// captureModeUiState: CaptureModeUiState + onSetCaptureMode: (CaptureMode) -> Unit, + onDisabledCaptureMode: (CaptureModeToggleUiState.DisabledReason) -> Unit, + captureModeUiState: CaptureModeUiState.Enabled ) { var isExpanded by remember { mutableStateOf(false) } - var selectedOption by remember { mutableStateOf("Default") } Column(modifier = modifier) { AnimatedVisibility( @@ -642,46 +647,86 @@ fun CaptureModeDropDown( fadeIn() + expandVertically(expandFrom = Alignment.Top), exit = shrinkVertically(shrinkTowards = Alignment.Bottom) ) { + fun onDisabledClick(selectableState: SingleSelectableState): () -> Unit = + if (selectableState is SingleSelectableState.Disabled) { + { onDisabledCaptureMode(selectableState.disabledReason) } + } else { + { TODO("Enabled should not have disabled click") } + } + Column { - Text( + DropDownItem( text = "Default", - modifier = Modifier - .clickable { - selectedOption = "Default" - isExpanded = false - } - .padding(16.dp) + enabled = captureModeUiState.defaultCaptureState + is SingleSelectableState.Selectable, + onClick = { + onSetCaptureMode(CaptureMode.DEFAULT) + isExpanded = false + }, + onDisabledClick = onDisabledClick(captureModeUiState.defaultCaptureState) ) - Text( + DropDownItem( text = "Image Only", - modifier = Modifier - .clickable { - selectedOption = "Image Only" - isExpanded = false - } - .padding(16.dp) + enabled = captureModeUiState.imageOnlyCaptureState + is SingleSelectableState.Selectable, + onClick = { + onSetCaptureMode(CaptureMode.IMAGE_ONLY) + isExpanded = false + }, + onDisabledClick = onDisabledClick(captureModeUiState.imageOnlyCaptureState) ) - Text( + DropDownItem( text = "Video Only", - modifier = Modifier - .clickable { - selectedOption = "Video Only" - isExpanded = false - } - .padding(16.dp) + enabled = captureModeUiState.videoOnlyCaptureState + is SingleSelectableState.Selectable, + onClick = { + onSetCaptureMode(CaptureMode.VIDEO_ONLY) + isExpanded = false + }, + onDisabledClick = onDisabledClick( + captureModeUiState.videoOnlyCaptureState + ) + ) } } + // current selection Box( modifier = Modifier .clickable { isExpanded = !isExpanded } .padding(8.dp) ) { - Text(text = selectedOption, modifier = Modifier.padding(16.dp)) + Text( + text = captureModeUiState.currentSelection.toString(), + modifier = Modifier.padding(16.dp) + ) } } } +@Composable +fun DropDownItem( + modifier: Modifier = Modifier, + text: String, + onClick: () -> Unit = {}, + onDisabledClick: () -> Unit = {}, + enabled: Boolean = true, + isSelected: Boolean = false +) { + Text( + text = text, + color = if (enabled) Color.Unspecified else Color.DarkGray, + modifier = modifier + .clickable(enabled = true, onClick = if (enabled) onClick else onDisabledClick) + .apply { + if (!enabled) { + alpha(.37f) + } + } + .padding(16.dp) + ) +} + @Composable fun CaptureButton( onClick: () -> Unit, @@ -733,6 +778,7 @@ fun CaptureButton( is VideoRecordingState.Active.Recording, is VideoRecordingState.Active.Paused -> Color.Red + VideoRecordingState.Starting -> currentColor } ) From 015048a4ba8ba273574bdc57310a2b0c8e0b3b80 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Wed, 18 Dec 2024 01:15:22 +0000 Subject: [PATCH 08/10] remove references to CaptureModeToggle --- ...ToggleUiState.kt => CaptureModeUiState.kt} | 47 +++++++++++++++++-- .../feature/preview/PreviewScreen.kt | 10 ++-- .../feature/preview/PreviewUiState.kt | 2 +- .../feature/preview/PreviewViewModel.kt | 37 +++++++-------- .../quicksettings/QuickSettingsScreen.kt | 5 +- .../preview/ui/CameraControlsOverlay.kt | 38 +++++++-------- .../preview/ui/PreviewScreenComponents.kt | 4 +- 7 files changed, 88 insertions(+), 55 deletions(-) rename feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/{CaptureModeToggleUiState.kt => CaptureModeUiState.kt} (73%) diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt similarity index 73% rename from feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt rename to feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt index 447a5b2d6..b00b68c05 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeToggleUiState.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/CaptureModeUiState.kt @@ -36,13 +36,53 @@ sealed interface CaptureModeUiState { ) : CaptureModeUiState } +enum class DisabledReason(val testTag: String, val reasonTextResId: Int) { + VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED( + VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG, + R.string.toast_video_capture_external_unsupported + ), + IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED( + IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG, + R.string.toast_image_capture_external_unsupported + + ), + IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA( + IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG, + R.string.toast_image_capture_unsupported_concurrent_camera + ), + HDR_VIDEO_UNSUPPORTED_ON_DEVICE( + HDR_VIDEO_UNSUPPORTED_ON_DEVICE_TAG, + R.string.toast_hdr_video_unsupported_on_device + ), + HDR_VIDEO_UNSUPPORTED_ON_LENS( + HDR_VIDEO_UNSUPPORTED_ON_LENS_TAG, + R.string.toast_hdr_video_unsupported_on_lens + ), + HDR_IMAGE_UNSUPPORTED_ON_DEVICE( + HDR_IMAGE_UNSUPPORTED_ON_DEVICE_TAG, + R.string.toast_hdr_photo_unsupported_on_device + ), + HDR_IMAGE_UNSUPPORTED_ON_LENS( + HDR_IMAGE_UNSUPPORTED_ON_LENS_TAG, + R.string.toast_hdr_photo_unsupported_on_lens + ), + HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM( + HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM_TAG, + R.string.toast_hdr_photo_unsupported_on_lens_single_stream + ), + HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM( + HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM_TAG, + R.string.toast_hdr_photo_unsupported_on_lens_multi_stream + ) +} + /** State for the individual options on Popup dialog settings */ sealed interface SingleSelectableState { data object Selectable : SingleSelectableState - data class Disabled(val disabledReason: CaptureModeToggleUiState.DisabledReason) : - SingleSelectableState + data class Disabled(val disabledReason: DisabledReason) : SingleSelectableState } +/* sealed interface CaptureModeToggleUiState { data object Invisible : CaptureModeToggleUiState @@ -100,4 +140,5 @@ sealed interface CaptureModeToggleUiState { CAPTURE_TOGGLE_IMAGE, CAPTURE_TOGGLE_VIDEO } -} + } + */ 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 37b6d2519..49acf8f73 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 @@ -149,7 +149,7 @@ fun PreviewScreen( onChangeDynamicRange = viewModel::setDynamicRange, onChangeConcurrentCameraMode = viewModel::setConcurrentCameraMode, onChangeImageFormat = viewModel::setImageFormat, - onToggleWhenDisabled = viewModel::showSnackBarForDisabledHdrToggle, + onDisabledCaptureMode = viewModel::showSnackBarForDisabledHdrToggle, onToggleQuickSettings = viewModel::toggleQuickSettings, onToggleDebugOverlay = viewModel::toggleDebugOverlay, onSetPause = viewModel::setPaused, @@ -185,7 +185,7 @@ private fun ContentScreen( onChangeDynamicRange: (DynamicRange) -> Unit = {}, onChangeConcurrentCameraMode: (ConcurrentCameraMode) -> Unit = {}, onChangeImageFormat: (ImageOutputFormat) -> Unit = {}, - onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {}, + onDisabledCaptureMode: (DisabledReason) -> Unit = {}, onToggleQuickSettings: () -> Unit = {}, onToggleDebugOverlay: () -> Unit = {}, onSetPause: (Boolean) -> Unit = {}, @@ -263,7 +263,7 @@ private fun ContentScreen( onToggleQuickSettings = onToggleQuickSettings, onToggleDebugOverlay = onToggleDebugOverlay, onChangeImageFormat = onChangeImageFormat, - onToggleWhenDisabled = onToggleWhenDisabled, + onDisabledCaptureMode = onDisabledCaptureMode, onSetPause = onSetPause, onCaptureImageWithUri = onCaptureImageWithUri, onStartVideoRecording = onStartVideoRecording, @@ -349,6 +349,6 @@ 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 + 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 e8381994c..714b1731f 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 @@ -43,7 +43,7 @@ sealed interface PreviewUiState { val snackBarToShow: SnackbarData? = null, val lastBlinkTimeStamp: Long = 0, val previewMode: PreviewMode = PreviewMode.StandardMode {}, - val captureModeToggleUiState: CaptureModeToggleUiState = CaptureModeToggleUiState.Invisible, + // val captureModeToggleUiState: CaptureModeToggleUiState = CaptureModeToggleUiState.Invisible, val sessionFirstFrameTimestamp: Long = 0L, val currentPhysicalCameraId: String? = null, val currentLogicalCameraId: String? = null, 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 6f6b8e5b2..bf14ee3f5 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 @@ -190,10 +190,10 @@ class PreviewViewModel @AssistedInject constructor( zoomScale = cameraState.zoomScale, videoRecordingState = cameraState.videoRecordingState, sessionFirstFrameTimestamp = cameraState.sessionFirstFrameTimestamp, - captureModeToggleUiState = getCaptureToggleUiState( + /*captureModeToggleUiState = getCaptureToggleUiState( systemConstraints, cameraAppSettings - ), + ),*/ currentLogicalCameraId = cameraState.debugInfo.logicalCameraId, currentPhysicalCameraId = cameraState.debugInfo.physicalCameraId, debugUiState = DebugUiState( @@ -354,7 +354,6 @@ class PreviewViewModel @AssistedInject constructor( systemConstraints: SystemConstraints, cameraAppSettings: CameraAppSettings ): CaptureModeUiState { - Log.d(TAG, "CREATING CAPMODE UISTATE") val cameraConstraints: CameraConstraints? = systemConstraints.forCurrentLens( cameraAppSettings ) @@ -375,16 +374,11 @@ class PreviewViewModel @AssistedInject constructor( } else { true } - Log.d(TAG, "current hdr video supported? $currentHdrDynamicRangeSupported") - - Log.d(TAG, "current hdr image supported? $currentHdrImageFormatSupported") - val supportedCaptureModes = getSupportedCaptureModes( cameraAppSettings, currentHdrDynamicRangeSupported, currentHdrImageFormatSupported ) - Log.d(TAG, "supported capture modes? $supportedCaptureModes") // if all capture modes are supported, return capturemodeuistate if (supportedCaptureModes.containsAll( listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) @@ -473,16 +467,16 @@ class PreviewViewModel @AssistedInject constructor( currentLensFacing: LensFacing, currentStreamConfig: StreamConfig, concurrentCameraMode: ConcurrentCameraMode - ): CaptureModeToggleUiState.DisabledReason { + ): DisabledReason { when (disabledCaptureMode) { CaptureMode.IMAGE_ONLY -> { if (previewMode is PreviewMode.ExternalVideoCaptureMode) { - return CaptureModeToggleUiState.DisabledReason + return DisabledReason .IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED } if (concurrentCameraMode == ConcurrentCameraMode.DUAL) { - return CaptureModeToggleUiState.DisabledReason + return DisabledReason .IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA } @@ -495,22 +489,22 @@ class PreviewViewModel @AssistedInject constructor( ) { return when (currentStreamConfig) { StreamConfig.MULTI_STREAM -> - CaptureModeToggleUiState.DisabledReason + DisabledReason .HDR_IMAGE_UNSUPPORTED_ON_MULTI_STREAM StreamConfig.SINGLE_STREAM -> - CaptureModeToggleUiState.DisabledReason + DisabledReason .HDR_IMAGE_UNSUPPORTED_ON_SINGLE_STREAM } } // Check if any other lens supports HDR image if (systemConstraints.anySupportsUltraHdr { it != currentLensFacing }) { - return CaptureModeToggleUiState.DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_LENS + return DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_LENS } // No lenses support HDR image on device - return CaptureModeToggleUiState.DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_DEVICE + return DisabledReason.HDR_IMAGE_UNSUPPORTED_ON_DEVICE } throw RuntimeException("Unknown DisabledReason for video mode.") @@ -518,15 +512,15 @@ class PreviewViewModel @AssistedInject constructor( CaptureMode.VIDEO_ONLY -> { if (previewMode is PreviewMode.ExternalImageCaptureMode) { - return CaptureModeToggleUiState.DisabledReason + return DisabledReason .VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED } if (!hdrDynamicRangeSupported) { if (systemConstraints.anySupportsHdrDynamicRange { it != currentLensFacing }) { - return CaptureModeToggleUiState.DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_LENS + return DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_LENS } - return CaptureModeToggleUiState.DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_DEVICE + return DisabledReason.HDR_VIDEO_UNSUPPORTED_ON_DEVICE } throw RuntimeException("Unknown DisabledReason for image mode.") @@ -537,6 +531,7 @@ class PreviewViewModel @AssistedInject constructor( } } + /* private fun getCaptureToggleUiState( systemConstraints: SystemConstraints, cameraAppSettings: CameraAppSettings @@ -592,6 +587,8 @@ class PreviewViewModel @AssistedInject constructor( } } + + private fun getCaptureToggleUiStateDisabledReason( captureModeToggleUiState: CaptureModeToggleUiState.ToggleMode, hdrDynamicRangeSupported: Boolean, @@ -661,6 +658,8 @@ class PreviewViewModel @AssistedInject constructor( } } + */ + private fun SystemConstraints.anySupportsHdrDynamicRange( lensFilter: (LensFacing) -> Boolean ): Boolean = perLensConstraints.asSequence().firstOrNull { @@ -904,7 +903,7 @@ class PreviewViewModel @AssistedInject constructor( } } - fun showSnackBarForDisabledHdrToggle(disabledReason: CaptureModeToggleUiState.DisabledReason) { + fun showSnackBarForDisabledHdrToggle(disabledReason: DisabledReason) { val cookieInt = snackBarCount.incrementAndGet() val cookie = "DisabledHdrToggle-$cookieInt" viewModelScope.launch { 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 ee21724d4..adca80b2f 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,7 +39,6 @@ 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.CaptureModeToggleUiState import com.google.jetpackcamera.feature.preview.FlashModeUiState import com.google.jetpackcamera.feature.preview.PreviewMode import com.google.jetpackcamera.feature.preview.PreviewUiState @@ -299,7 +298,7 @@ fun ExpandedQuickSettingsUiPreview() { systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, videoRecordingState = VideoRecordingState.Inactive(), - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, + // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, flashModeUiState = FlashModeUiState.Available( selectedFlashMode = FlashMode.OFF, availableFlashModes = listOf(FlashMode.OFF, FlashMode.ON) @@ -328,7 +327,7 @@ fun ExpandedQuickSettingsUiPreview_WithHdr() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, + // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Inactive() ), currentCameraSettings = CameraAppSettings(dynamicRange = 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 b6f87e205..81c7648cf 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 @@ -30,11 +30,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CameraAlt -import androidx.compose.material.icons.filled.Videocam -import androidx.compose.material.icons.outlined.CameraAlt -import androidx.compose.material.icons.outlined.Videocam import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable @@ -47,22 +42,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource 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.CaptureModeToggleUiState import com.google.jetpackcamera.feature.preview.CaptureModeUiState +import com.google.jetpackcamera.feature.preview.DisabledReason import com.google.jetpackcamera.feature.preview.FlashModeUiState import com.google.jetpackcamera.feature.preview.MultipleEventsCutter import com.google.jetpackcamera.feature.preview.PreviewMode import com.google.jetpackcamera.feature.preview.PreviewUiState import com.google.jetpackcamera.feature.preview.PreviewViewModel -import com.google.jetpackcamera.feature.preview.R 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 @@ -100,7 +92,7 @@ fun CameraControlsOverlay( onFlipCamera: () -> Unit = {}, onChangeFlash: (FlashMode) -> Unit = {}, onChangeImageFormat: (ImageOutputFormat) -> Unit = {}, - onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {}, + onDisabledCaptureMode: (DisabledReason) -> Unit = {}, onToggleQuickSettings: () -> Unit = {}, onToggleDebugOverlay: () -> Unit = {}, onMuteAudio: () -> Unit = {}, @@ -169,7 +161,7 @@ fun CameraControlsOverlay( onToggleAudioMuted = onMuteAudio, onSetPause = onSetPause, onChangeImageFormat = onChangeImageFormat, - onToggleWhenDisabled = onToggleWhenDisabled, + onDisabledCaptureMode = onDisabledCaptureMode, onStartVideoRecording = onStartVideoRecording, onStopVideoRecording = onStopVideoRecording ) @@ -265,7 +257,7 @@ private fun ControlsBottom( onSetPause: (Boolean) -> Unit = {}, onChangeImageFormat: (ImageOutputFormat) -> Unit = {}, onSetCaptureMode: (CaptureMode) -> Unit = {}, - onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {}, + onDisabledCaptureMode: (DisabledReason) -> Unit = {}, onStartVideoRecording: ( Uri?, Boolean, @@ -282,7 +274,7 @@ private fun ControlsBottom( modifier = Modifier.align(Alignment.BottomEnd), onSetCaptureMode = onSetCaptureMode, captureModeUiState = previewUiState.captureModeUiState, - onDisabledCaptureMode = onToggleWhenDisabled + onDisabledCaptureMode = onDisabledCaptureMode ) } } @@ -355,18 +347,20 @@ private fun ControlsBottom( onToggleMute = onToggleAudioMuted, audioAmplitude = videoRecordingState.audioAmplitude ) - } else { + } + /* else { if (!isQuickSettingsOpen && previewUiState.captureModeToggleUiState is CaptureModeToggleUiState.Visible ) { CaptureModeToggleButton( uiState = previewUiState.captureModeToggleUiState, onChangeImageFormat = onChangeImageFormat, - onToggleWhenDisabled = onToggleWhenDisabled, + onToggleWhenDisabled = onDisabledCaptureMode, modifier = Modifier.testTag(CAPTURE_MODE_TOGGLE_BUTTON) ) } } + */ } } } @@ -472,7 +466,7 @@ private fun CaptureButton( videoRecordingState = videoRecordingState ) } - +/* @Composable private fun CaptureModeToggleButton( uiState: CaptureModeToggleUiState.Visible, @@ -520,7 +514,7 @@ private fun CaptureModeToggleButton( stringResource(id = R.string.capture_mode_video_recording_content_description), modifier = modifier ) -} +}*/ @Preview(backgroundColor = 0xFF000000, showBackground = true) @Composable @@ -605,7 +599,7 @@ private fun Preview_ControlsBottom() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, +// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Inactive() ), zoomLevel = 1.3f, @@ -626,7 +620,7 @@ private fun Preview_ControlsBottom_NoZoomLevel() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, + // captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Inactive() ), zoomLevel = 1.3f, @@ -647,7 +641,7 @@ private fun Preview_ControlsBottom_QuickSettingsOpen() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, +// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Inactive() ), zoomLevel = 1.3f, @@ -668,7 +662,7 @@ private fun Preview_ControlsBottom_NoFlippableCamera() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, +// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Inactive() ), zoomLevel = 1.3f, @@ -695,7 +689,7 @@ private fun Preview_ControlsBottom_Recording() { currentCameraSettings = CameraAppSettings(), systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS, previewMode = PreviewMode.StandardMode {}, - captureModeToggleUiState = CaptureModeToggleUiState.Invisible, +// captureModeToggleUiState = CaptureModeToggleUiState.Invisible, videoRecordingState = VideoRecordingState.Active.Recording(0L, .9, 1_000_000_000) ), 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 071f1798e..f1d3b5914 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,8 +106,8 @@ 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.CaptureModeToggleUiState import com.google.jetpackcamera.feature.preview.CaptureModeUiState +import com.google.jetpackcamera.feature.preview.DisabledReason import com.google.jetpackcamera.feature.preview.PreviewUiState import com.google.jetpackcamera.feature.preview.R import com.google.jetpackcamera.feature.preview.SingleSelectableState @@ -635,7 +635,7 @@ fun CurrentCameraIdText(physicalCameraId: String?, logicalCameraId: String?) { fun CaptureModeDropDown( modifier: Modifier = Modifier, onSetCaptureMode: (CaptureMode) -> Unit, - onDisabledCaptureMode: (CaptureModeToggleUiState.DisabledReason) -> Unit, + onDisabledCaptureMode: (DisabledReason) -> Unit, captureModeUiState: CaptureModeUiState.Enabled ) { var isExpanded by remember { mutableStateOf(false) } From c16d183a587e3bfc9e4c748c8d9467baeeb0f71a Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Wed, 29 Jan 2025 12:51:53 +0000 Subject: [PATCH 09/10] rename default to standard. string resources for capture mode ui component --- .../jetpackcamera/core/camera/CameraSession.kt | 2 +- .../core/camera/CameraXCameraUseCase.kt | 2 +- .../settings/model/CameraAppSettings.kt | 2 +- .../jetpackcamera/settings/model/CaptureMode.kt | 2 +- .../feature/preview/PreviewViewModel.kt | 10 +++++----- .../preview/ui/PreviewScreenComponents.kt | 16 ++++++++++------ feature/preview/src/main/res/values/strings.xml | 6 +++++- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt index 9d0b80b5e..e573cdb24 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt @@ -110,7 +110,7 @@ internal suspend fun runSingleCameraSession( .primaryLensFacing.toCameraSelector() val videoCaptureUseCase = when (sessionSettings.captureMode) { - CaptureMode.DEFAULT, CaptureMode.VIDEO_ONLY -> + CaptureMode.STANDARD, CaptureMode.VIDEO_ONLY -> createVideoUseCase( cameraProvider.getCameraInfo(initialCameraSelector), sessionSettings.aspectRatio, 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..7237d1101 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 @@ -607,7 +607,7 @@ constructor( private fun CameraAppSettings.tryApplyAspectRatioForExternalCapture( captureMode: CaptureMode ): CameraAppSettings = when (captureMode) { - CaptureMode.DEFAULT -> this + CaptureMode.STANDARD -> this CaptureMode.IMAGE_ONLY -> this.copy(aspectRatio = AspectRatio.THREE_FOUR) CaptureMode.VIDEO_ONLY -> diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt index 03868b7cf..4d03d2021 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/model/CameraAppSettings.kt @@ -23,7 +23,7 @@ val DEFAULT_HDR_IMAGE_OUTPUT = ImageOutputFormat.JPEG_ULTRA_HDR * Data layer representation for settings. */ data class CameraAppSettings( - val captureMode: CaptureMode = CaptureMode.DEFAULT, + val captureMode: CaptureMode = CaptureMode.STANDARD, val cameraLensFacing: LensFacing = LensFacing.BACK, val darkMode: DarkMode = DarkMode.SYSTEM, val flashMode: FlashMode = FlashMode.OFF, 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..5b5bffda3 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 @@ -16,7 +16,7 @@ package com.google.jetpackcamera.settings.model enum class CaptureMode { - DEFAULT, + STANDARD, VIDEO_ONLY, IMAGE_ONLY } 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 47cd50276..b9522504c 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 @@ -125,7 +125,7 @@ class PreviewViewModel @AssistedInject constructor( */ private fun CameraAppSettings.applyPreviewMode(previewMode: PreviewMode): CameraAppSettings { val captureMode = previewMode.toCaptureMode() - return if (captureMode == CaptureMode.DEFAULT) { + return if (captureMode == CaptureMode.STANDARD) { this } else { this.copy(captureMode = captureMode) @@ -301,7 +301,7 @@ class PreviewViewModel @AssistedInject constructor( is PreviewMode.ExternalImageCaptureMode -> CaptureMode.IMAGE_ONLY is PreviewMode.ExternalMultipleImageCaptureMode -> CaptureMode.IMAGE_ONLY is PreviewMode.ExternalVideoCaptureMode -> CaptureMode.VIDEO_ONLY - is PreviewMode.StandardMode -> CaptureMode.DEFAULT + is PreviewMode.StandardMode -> CaptureMode.STANDARD } /** @@ -391,7 +391,7 @@ class PreviewViewModel @AssistedInject constructor( ) // if all capture modes are supported, return capturemodeuistate if (supportedCaptureModes.containsAll( - listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + listOf(CaptureMode.STANDARD, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) ) ) { return CaptureModeUiState.Enabled(currentSelection = cameraAppSettings.captureMode) @@ -458,7 +458,7 @@ class PreviewViewModel @AssistedInject constructor( currentHdrImageFormatSupported && cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF ) { - listOf(CaptureMode.DEFAULT, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) + listOf(CaptureMode.STANDARD, CaptureMode.IMAGE_ONLY, CaptureMode.VIDEO_ONLY) } else if ( cameraAppSettings.concurrentCameraMode == ConcurrentCameraMode.OFF && previewMode is PreviewMode.ExternalImageCaptureMode || @@ -535,7 +535,7 @@ class PreviewViewModel @AssistedInject constructor( throw RuntimeException("Unknown DisabledReason for image mode.") } - CaptureMode.DEFAULT -> { + CaptureMode.STANDARD -> { TODO() } } 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..81e83471e 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 @@ -656,17 +656,17 @@ fun CaptureModeDropDown( Column { DropDownItem( - text = "Default", + text = stringResource(R.string.capture_mode_text_standard), enabled = captureModeUiState.defaultCaptureState is SingleSelectableState.Selectable, onClick = { - onSetCaptureMode(CaptureMode.DEFAULT) + onSetCaptureMode(CaptureMode.STANDARD) isExpanded = false }, onDisabledClick = onDisabledClick(captureModeUiState.defaultCaptureState) ) DropDownItem( - text = "Image Only", + text = stringResource(R.string.capture_mode_text_image_only), enabled = captureModeUiState.imageOnlyCaptureState is SingleSelectableState.Selectable, onClick = { @@ -676,7 +676,7 @@ fun CaptureModeDropDown( onDisabledClick = onDisabledClick(captureModeUiState.imageOnlyCaptureState) ) DropDownItem( - text = "Video Only", + text = stringResource(R.string.capture_mode_text_video_only), enabled = captureModeUiState.videoOnlyCaptureState is SingleSelectableState.Selectable, onClick = { @@ -690,14 +690,18 @@ fun CaptureModeDropDown( ) } } - // current selection + // this text displays the current selection Box( modifier = Modifier .clickable { isExpanded = !isExpanded } .padding(8.dp) ) { Text( - text = captureModeUiState.currentSelection.toString(), + text = when (captureModeUiState.currentSelection) { + CaptureMode.STANDARD -> stringResource(R.string.capture_mode_text_standard) + CaptureMode.VIDEO_ONLY -> stringResource(R.string.capture_mode_text_video_only) + CaptureMode.IMAGE_ONLY -> stringResource(R.string.capture_mode_text_image_only) + }, modifier = Modifier.padding(16.dp) ) } diff --git a/feature/preview/src/main/res/values/strings.xml b/feature/preview/src/main/res/values/strings.xml index f899947fe..235b580e8 100644 --- a/feature/preview/src/main/res/values/strings.xml +++ b/feature/preview/src/main/res/values/strings.xml @@ -28,6 +28,10 @@ Physical ID: Logical ID: + + Standard + Video Only + Image Only Image Capture Success Video Capture Success @@ -48,7 +52,7 @@ HDR video not supported on this device HDR video not supported by current lens - + FRONT BACK Front Camera From 76021141887b03ed694c5009c15bcc29d74dfe0b Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Fri, 31 Jan 2025 02:21:44 +0000 Subject: [PATCH 10/10] minor visual changes --- .../feature/preview/ui/PreviewScreenComponents.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 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 index 3acb4f2c0..2d89e498f 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 @@ -21,6 +21,7 @@ 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.core.ImplementationMode @@ -40,6 +41,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -117,13 +119,12 @@ import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.StabilizationMode import com.google.jetpackcamera.settings.model.VideoQuality +import kotlin.time.Duration.Companion.nanoseconds 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 kotlin.time.Duration.Companion.nanoseconds -import androidx.camera.core.DynamicRange as CXDynamicRange private const val TAG = "PreviewScreen" private const val BLINK_TIME = 100L @@ -741,7 +742,12 @@ fun CaptureModeDropDown( // this text displays the current selection Box( modifier = Modifier - .clickable { isExpanded = !isExpanded } + .clickable( + interactionSource = remember { MutableInteractionSource() }, + // removes the greyish background animation that appears when clicking on a clickable + indication = null, + onClick = { isExpanded = !isExpanded } + ) .padding(8.dp) ) { Text(