From 634a30f8d73d27d85de5e5a08618a249a25440fa Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 25 Nov 2025 15:17:19 -0800 Subject: [PATCH 1/2] Move UiState management and extension functions out of PreviewViewModel CaptureUiStateAdapter, QuickSettingsUiStateAdapter, DebugUiStateAdapter are created to create/update UiStates. Extension functions are turned into companion functions of the appropriate model/setting. --- .../jetpackcamera/core/camera/CameraSystem.kt | 46 +++ .../model/ExternalCaptureMode.kt | 11 +- .../settings/model/CameraAppSettings.kt | 18 ++ .../feature/preview/PreviewViewModel.kt | 268 ++---------------- .../ui/uistate/capture/DebugUiState.kt | 10 +- .../capture/compound/CaptureUiState.kt | 2 + .../capture/compound/QuickSettingsUiState.kt | 2 + .../capture/DebugUiStateAdapter.kt | 33 ++- .../capture/compound/CaptureUiStateAdapter.kt | 178 ++++++++++++ .../compound/QuickSettingsUiStateAdapter.kt | 62 ++++ 10 files changed, 370 insertions(+), 260 deletions(-) create mode 100644 ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt create mode 100644 ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/QuickSettingsUiStateAdapter.kt diff --git a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt index ec2ccfb90..b6821300e 100644 --- a/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt +++ b/core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt @@ -140,4 +140,50 @@ interface CameraSystem { CLEAR_UI } } + + companion object { + /** + * Applies an individual camera app setting with the given [settingExtractor] and + * [settingApplicator] if the new setting differs from the old setting. + */ + private suspend inline fun CameraAppSettings.applyDiff( + new: CameraAppSettings, + settingExtractor: CameraAppSettings.() -> R, + crossinline settingApplicator: suspend (R) -> Unit + ) { + val oldSetting = settingExtractor.invoke(this) + val newSetting = settingExtractor.invoke(new) + if (oldSetting != newSetting) { + settingApplicator(newSetting) + } + } + + /** + * Checks whether each actionable individual setting has changed and applies them to + * [CameraSystem]. + */ + suspend fun CameraAppSettings.applyDiffs( + new: CameraAppSettings, + cameraSystem: CameraSystem + ) { + applyDiff(new, CameraAppSettings::cameraLensFacing, cameraSystem::setLensFacing) + applyDiff(new, CameraAppSettings::flashMode, cameraSystem::setFlashMode) + applyDiff(new, CameraAppSettings::streamConfig, cameraSystem::setStreamConfig) + applyDiff(new, CameraAppSettings::aspectRatio, cameraSystem::setAspectRatio) + applyDiff(new, CameraAppSettings::stabilizationMode, cameraSystem::setStabilizationMode) + applyDiff(new, CameraAppSettings::targetFrameRate, cameraSystem::setTargetFrameRate) + applyDiff( + new, + CameraAppSettings::maxVideoDurationMillis, + cameraSystem::setMaxVideoDuration + ) + applyDiff(new, CameraAppSettings::videoQuality, cameraSystem::setVideoQuality) + applyDiff(new, CameraAppSettings::audioEnabled, cameraSystem::setAudioEnabled) + applyDiff( + new, + CameraAppSettings::lowLightBoostPriority, + cameraSystem::setLowLightBoostPriority + ) + } + } } diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/ExternalCaptureMode.kt b/core/model/src/main/java/com/google/jetpackcamera/model/ExternalCaptureMode.kt index bcfde518e..1454d0523 100644 --- a/core/model/src/main/java/com/google/jetpackcamera/model/ExternalCaptureMode.kt +++ b/core/model/src/main/java/com/google/jetpackcamera/model/ExternalCaptureMode.kt @@ -38,5 +38,14 @@ enum class ExternalCaptureMode { /** * Under this mode, the app is launched by an external intent to capture multiple images. */ - MultipleImageCapture + MultipleImageCapture; + + companion object { + fun ExternalCaptureMode.toCaptureMode() = when (this) { + ImageCapture -> CaptureMode.IMAGE_ONLY + MultipleImageCapture -> CaptureMode.IMAGE_ONLY + VideoCapture -> CaptureMode.VIDEO_ONLY + Standard -> null + } + } } 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 8761e48ea..d3d388def 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 @@ -22,6 +22,8 @@ import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DebugSettings import com.google.jetpackcamera.model.DeviceRotation import com.google.jetpackcamera.model.DynamicRange +import com.google.jetpackcamera.model.ExternalCaptureMode +import com.google.jetpackcamera.model.ExternalCaptureMode.Companion.toCaptureMode import com.google.jetpackcamera.model.FlashMode import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing @@ -61,4 +63,20 @@ fun CameraSystemConstraints.forCurrentLens( cameraAppSettings: CameraAppSettings ): CameraConstraints? = perLensConstraints[cameraAppSettings.cameraLensFacing] +/** + * updates the capture mode based on the preview mode + */ +fun CameraAppSettings.applyExternalCaptureMode( + externalCaptureMode: ExternalCaptureMode +): CameraAppSettings { + val requiredCaptureModeOverride = externalCaptureMode.toCaptureMode() + return if (requiredCaptureModeOverride == null || + requiredCaptureModeOverride == this.captureMode + ) { + this + } else { + this.copy(captureMode = requiredCaptureModeOverride) + } +} + val DEFAULT_CAMERA_APP_SETTINGS = CameraAppSettings() 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 e91f10281..7500f4993 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 @@ -25,8 +25,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.tracing.Trace import androidx.tracing.traceAsync -import com.google.jetpackcamera.core.camera.CameraState import com.google.jetpackcamera.core.camera.CameraSystem +import com.google.jetpackcamera.core.camera.CameraSystem.Companion.applyDiffs import com.google.jetpackcamera.core.camera.OnVideoRecordEvent import com.google.jetpackcamera.core.common.DefaultSaveMode import com.google.jetpackcamera.core.common.traceFirstFramePreview @@ -59,7 +59,7 @@ import com.google.jetpackcamera.model.VideoCaptureEvent import com.google.jetpackcamera.settings.ConstraintsRepository import com.google.jetpackcamera.settings.SettingsRepository import com.google.jetpackcamera.settings.model.CameraAppSettings -import com.google.jetpackcamera.settings.model.CameraSystemConstraints +import com.google.jetpackcamera.settings.model.applyExternalCaptureMode import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_FAILURE_TAG import com.google.jetpackcamera.ui.components.capture.IMAGE_CAPTURE_SUCCESS_TAG @@ -70,31 +70,14 @@ import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_EXTERNAL_UNS import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_FAILURE_TAG import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG import com.google.jetpackcamera.ui.uistate.DisableRationale -import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState -import com.google.jetpackcamera.ui.uistate.capture.AudioUiState -import com.google.jetpackcamera.ui.uistate.capture.CaptureButtonUiState -import com.google.jetpackcamera.ui.uistate.capture.CaptureModeToggleUiState -import com.google.jetpackcamera.ui.uistate.capture.CaptureModeUiState -import com.google.jetpackcamera.ui.uistate.capture.ConcurrentCameraUiState -import com.google.jetpackcamera.ui.uistate.capture.DebugUiState -import com.google.jetpackcamera.ui.uistate.capture.ElapsedTimeUiState -import com.google.jetpackcamera.ui.uistate.capture.FlashModeUiState -import com.google.jetpackcamera.ui.uistate.capture.FlipLensUiState -import com.google.jetpackcamera.ui.uistate.capture.FocusMeteringUiState -import com.google.jetpackcamera.ui.uistate.capture.HdrUiState import com.google.jetpackcamera.ui.uistate.capture.ImageWellUiState import com.google.jetpackcamera.ui.uistate.capture.SnackBarUiState import com.google.jetpackcamera.ui.uistate.capture.SnackbarData -import com.google.jetpackcamera.ui.uistate.capture.StabilizationUiState -import com.google.jetpackcamera.ui.uistate.capture.StreamConfigUiState -import com.google.jetpackcamera.ui.uistate.capture.ZoomControlUiState -import com.google.jetpackcamera.ui.uistate.capture.ZoomUiState import com.google.jetpackcamera.ui.uistate.capture.compound.CaptureUiState import com.google.jetpackcamera.ui.uistate.capture.compound.FocusedQuickSetting import com.google.jetpackcamera.ui.uistate.capture.compound.PreviewDisplayUiState -import com.google.jetpackcamera.ui.uistate.capture.compound.QuickSettingsUiState +import com.google.jetpackcamera.ui.uistateadapter.capture.compound.update import com.google.jetpackcamera.ui.uistateadapter.capture.from -import com.google.jetpackcamera.ui.uistateadapter.capture.updateFrom import dagger.hilt.android.lifecycle.HiltViewModel import java.util.LinkedList import javax.inject.Inject @@ -173,22 +156,6 @@ class PreviewViewModel @Inject constructor( ) { cameraPropertiesJSON = it } } - /** - * updates the capture mode based on the preview mode - */ - private fun CameraAppSettings.applyExternalCaptureMode( - externalCaptureMode: ExternalCaptureMode - ): CameraAppSettings { - val requiredCaptureModeOverride = externalCaptureMode.toCaptureMode() - return if (requiredCaptureModeOverride == null || - requiredCaptureModeOverride == this.captureMode - ) { - this - } else { - this.copy(captureMode = requiredCaptureModeOverride) - } - } - init { viewModelScope.launch { launch { @@ -196,7 +163,7 @@ class PreviewViewModel @Inject constructor( settingsRepository.defaultCameraAppSettings .collect { new -> oldCameraAppSettings?.apply { - applyDiffs(new) + applyDiffs(new, cameraSystem) } oldCameraAppSettings = new } @@ -228,185 +195,32 @@ class PreviewViewModel @Inject constructor( cameraSystem.getCurrentCameraState(), trackedPreviewUiState ) { cameraAppSettings, systemConstraints, cameraState, trackedUiState -> - - var flashModeUiState: FlashModeUiState - var focusMeteringUiState: FocusMeteringUiState - - val captureModeUiState = CaptureModeUiState.from( - systemConstraints, - cameraAppSettings, - externalCaptureMode - ) - val flipLensUiState = FlipLensUiState.from( - cameraAppSettings, - systemConstraints - ) - val aspectRatioUiState = AspectRatioUiState.from(cameraAppSettings) - val hdrUiState = HdrUiState.from( + CaptureUiState.update( + _captureUiState, cameraAppSettings, systemConstraints, - externalCaptureMode + cameraState, + externalCaptureMode, + debugSettings, + cameraPropertiesJSON, + trackedUiState.isQuickSettingsOpen, + trackedUiState.focusedQuickSetting, + trackedUiState.isDebugOverlayOpen, + trackedUiState.isRecordingLocked, + trackedUiState.zoomAnimationTarget, + trackedUiState.debugHidingComponents, + trackedUiState.recentCapturedMedia ) - _captureUiState.update { old -> - when (old) { - is CaptureUiState.NotReady -> { - flashModeUiState = FlashModeUiState.from( - cameraAppSettings, - systemConstraints - ) - focusMeteringUiState = FocusMeteringUiState.from(cameraState) - // This is the first PreviewUiState.Ready. Create the initial - // PreviewUiState.Ready from defaults and initialize it below. - CaptureUiState.Ready() - } - - is CaptureUiState.Ready -> { - flashModeUiState = old.flashModeUiState.updateFrom( - cameraAppSettings = cameraAppSettings, - systemConstraints = systemConstraints, - cameraState = cameraState - ) - - focusMeteringUiState = old.focusMeteringUiState.updateFrom(cameraState) - // We have a previous `PreviewUiState.Ready`, return it here and - // update it below. - old - } - }.copy( - // Update or initialize PreviewUiState.Ready - externalCaptureMode = externalCaptureMode, - videoRecordingState = cameraState.videoRecordingState, - flipLensUiState = flipLensUiState, - aspectRatioUiState = aspectRatioUiState, - previewDisplayUiState = PreviewDisplayUiState(0, aspectRatioUiState), - quickSettingsUiState = getQuickSettingsUiState( - captureModeUiState, - flashModeUiState, - flipLensUiState, - cameraAppSettings, - systemConstraints, - aspectRatioUiState, - hdrUiState, - trackedUiState.isQuickSettingsOpen, - trackedUiState.focusedQuickSetting - ), - sessionFirstFrameTimestamp = cameraState.sessionFirstFrameTimestamp, - debugUiState = getDebugUiState( - systemConstraints, - cameraAppSettings, - cameraState, - trackedUiState.isDebugOverlayOpen, - trackedUiState.debugHidingComponents - ), - stabilizationUiState = StabilizationUiState.from( - cameraAppSettings, - cameraState - ), - flashModeUiState = flashModeUiState, - videoQuality = cameraState.videoQualityInfo.quality, - audioUiState = AudioUiState.from( - cameraAppSettings, - cameraState - ), - elapsedTimeUiState = ElapsedTimeUiState.from(cameraState), - captureButtonUiState = CaptureButtonUiState.from( - cameraAppSettings, - cameraState, - trackedUiState.isRecordingLocked - ), - zoomUiState = ZoomUiState.from( - systemConstraints, - cameraAppSettings.cameraLensFacing, - cameraState - ), - zoomControlUiState = ZoomControlUiState.from( - trackedUiState.zoomAnimationTarget, - systemConstraints, - cameraAppSettings, - cameraState - ), - captureModeToggleUiState = CaptureModeToggleUiState.from( - systemConstraints, - cameraAppSettings, - cameraState, - externalCaptureMode - ), - hdrUiState = hdrUiState, - focusMeteringUiState = focusMeteringUiState, - imageWellUiState = ImageWellUiState.from( - trackedUiState.recentCapturedMedia, - cameraState.videoRecordingState - ) - - ) - } }.collect {} } } - private fun getQuickSettingsUiState( - captureModeUiState: CaptureModeUiState, - flashModeUiState: FlashModeUiState, - flipLensUiState: FlipLensUiState, - cameraAppSettings: CameraAppSettings, - systemConstraints: CameraSystemConstraints, - aspectRatioUiState: AspectRatioUiState, - hdrUiState: HdrUiState, - quickSettingsIsOpen: Boolean, - focusedQuickSetting: FocusedQuickSetting - ): QuickSettingsUiState { - val streamConfigUiState = StreamConfigUiState.from(cameraAppSettings) - return QuickSettingsUiState.Available( - aspectRatioUiState = aspectRatioUiState, - captureModeUiState = captureModeUiState, - concurrentCameraUiState = ConcurrentCameraUiState.from( - cameraAppSettings, - systemConstraints, - externalCaptureMode, - captureModeUiState, - streamConfigUiState - ), - flashModeUiState = flashModeUiState, - flipLensUiState = flipLensUiState, - hdrUiState = hdrUiState, - streamConfigUiState = streamConfigUiState, - quickSettingsIsOpen = quickSettingsIsOpen, - focusedQuickSetting = focusedQuickSetting - ) - } - fun toggleDebugHidingComponents() { trackedPreviewUiState.update { old -> old.copy(debugHidingComponents = !old.debugHidingComponents) } } - private fun getDebugUiState( - systemConstraints: CameraSystemConstraints, - cameraAppSettings: CameraAppSettings, - cameraState: CameraState, - isDebugOverlayOpen: Boolean, - debugHidingComponents: Boolean - ): DebugUiState = if (debugSettings.isDebugModeEnabled) { - if (isDebugOverlayOpen) { - DebugUiState.Enabled.Open.from( - systemConstraints, - cameraAppSettings, - cameraState, - debugHidingComponents, - cameraPropertiesJSON - ) - } else { - DebugUiState.Enabled.Closed.from( - cameraState, - cameraAppSettings.cameraLensFacing, - debugHidingComponents - ) - } - } else { - DebugUiState.Disabled - } - /** * Sets the media from the image well to the [MediaRepository]. */ @@ -430,54 +244,6 @@ class PreviewViewModel @Inject constructor( } } - private fun ExternalCaptureMode.toCaptureMode() = when (this) { - ExternalCaptureMode.ImageCapture -> CaptureMode.IMAGE_ONLY - ExternalCaptureMode.MultipleImageCapture -> CaptureMode.IMAGE_ONLY - ExternalCaptureMode.VideoCapture -> CaptureMode.VIDEO_ONLY - ExternalCaptureMode.Standard -> null - } - - /** - * Applies an individual camera app setting with the given [settingExtractor] and - * [settingApplicator] if the new setting differs from the old setting. - */ - private suspend inline fun CameraAppSettings.applyDiff( - new: CameraAppSettings, - settingExtractor: CameraAppSettings.() -> R, - crossinline settingApplicator: suspend (R) -> Unit - ) { - val oldSetting = settingExtractor.invoke(this) - val newSetting = settingExtractor.invoke(new) - if (oldSetting != newSetting) { - settingApplicator(newSetting) - } - } - - /** - * Checks whether each actionable individual setting has changed and applies them to - * [CameraSystem]. - */ - private suspend fun CameraAppSettings.applyDiffs(new: CameraAppSettings) { - applyDiff(new, CameraAppSettings::cameraLensFacing, cameraSystem::setLensFacing) - applyDiff(new, CameraAppSettings::flashMode, cameraSystem::setFlashMode) - applyDiff(new, CameraAppSettings::streamConfig, cameraSystem::setStreamConfig) - applyDiff(new, CameraAppSettings::aspectRatio, cameraSystem::setAspectRatio) - applyDiff(new, CameraAppSettings::stabilizationMode, cameraSystem::setStabilizationMode) - applyDiff(new, CameraAppSettings::targetFrameRate, cameraSystem::setTargetFrameRate) - applyDiff( - new, - CameraAppSettings::maxVideoDurationMillis, - cameraSystem::setMaxVideoDuration - ) - applyDiff(new, CameraAppSettings::videoQuality, cameraSystem::setVideoQuality) - applyDiff(new, CameraAppSettings::audioEnabled, cameraSystem::setAudioEnabled) - applyDiff( - new, - CameraAppSettings::lowLightBoostPriority, - cameraSystem::setLowLightBoostPriority - ) - } - fun startCamera() { Log.d(TAG, "startCamera") stopCamera() diff --git a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/DebugUiState.kt b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/DebugUiState.kt index 4cf655863..146011e39 100644 --- a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/DebugUiState.kt +++ b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/DebugUiState.kt @@ -32,9 +32,7 @@ sealed interface DebugUiState { override val currentLogicalCameraId: String? = null, override val currentPrimaryZoomRatio: Float?, override val debugHidingComponents: Boolean = false - ) : Enabled { - companion object - } + ) : Enabled data class Open( override val currentPhysicalCameraId: String? = null, @@ -45,8 +43,8 @@ sealed interface DebugUiState { val videoResolution: Size? = null, val selectedTestPattern: TestPattern = TestPattern.Off, val availableTestPatterns: Set = setOf(TestPattern.Off) - ) : Enabled { - companion object - } + ) : Enabled } + + companion object } diff --git a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/CaptureUiState.kt b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/CaptureUiState.kt index c9f366e33..85836c521 100644 --- a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/CaptureUiState.kt +++ b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/CaptureUiState.kt @@ -68,4 +68,6 @@ sealed interface CaptureUiState { val hdrUiState: HdrUiState = HdrUiState.Unavailable, val focusMeteringUiState: FocusMeteringUiState = FocusMeteringUiState.Unspecified ) : CaptureUiState + + companion object } diff --git a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/QuickSettingsUiState.kt b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/QuickSettingsUiState.kt index 6c4e8e22e..9a6cd679f 100644 --- a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/QuickSettingsUiState.kt +++ b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/QuickSettingsUiState.kt @@ -37,6 +37,8 @@ sealed interface QuickSettingsUiState { val quickSettingsIsOpen: Boolean = false, val focusedQuickSetting: FocusedQuickSetting = FocusedQuickSetting.NONE ) : QuickSettingsUiState + + companion object } // enum representing which individual quick setting is currently focused diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DebugUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DebugUiStateAdapter.kt index 0af3f3cb1..f44920246 100644 --- a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DebugUiStateAdapter.kt +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DebugUiStateAdapter.kt @@ -17,6 +17,7 @@ package com.google.jetpackcamera.ui.uistateadapter.capture import android.util.Size import com.google.jetpackcamera.core.camera.CameraState +import com.google.jetpackcamera.model.DebugSettings import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.model.TestPattern import com.google.jetpackcamera.settings.model.CameraAppSettings @@ -24,7 +25,35 @@ import com.google.jetpackcamera.settings.model.CameraSystemConstraints import com.google.jetpackcamera.settings.model.forCurrentLens import com.google.jetpackcamera.ui.uistate.capture.DebugUiState -fun DebugUiState.Enabled.Open.Companion.from( +fun DebugUiState.Companion.from( + systemConstraints: CameraSystemConstraints, + cameraAppSettings: CameraAppSettings, + cameraState: CameraState, + isDebugOverlayOpen: Boolean, + debugHidingComponents: Boolean, + debugSettings: DebugSettings, + cameraPropertiesJSON: String +): DebugUiState = if (debugSettings.isDebugModeEnabled) { + if (isDebugOverlayOpen) { + getEnabledDebugUiState( + systemConstraints, + cameraAppSettings, + cameraState, + debugHidingComponents, + cameraPropertiesJSON + ) + } else { + getDisabledDebugUiState( + cameraState, + cameraAppSettings.cameraLensFacing, + debugHidingComponents + ) + } +} else { + DebugUiState.Disabled +} + +private fun getEnabledDebugUiState( systemConstraints: CameraSystemConstraints, cameraAppSettings: CameraAppSettings, cameraState: CameraState, @@ -57,7 +86,7 @@ fun DebugUiState.Enabled.Open.Companion.from( ) } -fun DebugUiState.Enabled.Closed.Companion.from( +private fun getDisabledDebugUiState( cameraState: CameraState, lensFacing: LensFacing, debugHidingComponents: Boolean diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt new file mode 100644 index 000000000..2b6a46c60 --- /dev/null +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 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.ui.uistateadapter.capture.compound + +import com.google.jetpackcamera.core.camera.CameraState +import com.google.jetpackcamera.data.media.MediaDescriptor +import com.google.jetpackcamera.model.DebugSettings +import com.google.jetpackcamera.model.ExternalCaptureMode +import com.google.jetpackcamera.settings.model.CameraAppSettings +import com.google.jetpackcamera.settings.model.CameraSystemConstraints +import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState +import com.google.jetpackcamera.ui.uistate.capture.AudioUiState +import com.google.jetpackcamera.ui.uistate.capture.CaptureButtonUiState +import com.google.jetpackcamera.ui.uistate.capture.CaptureModeToggleUiState +import com.google.jetpackcamera.ui.uistate.capture.CaptureModeUiState +import com.google.jetpackcamera.ui.uistate.capture.DebugUiState +import com.google.jetpackcamera.ui.uistate.capture.ElapsedTimeUiState +import com.google.jetpackcamera.ui.uistate.capture.FlashModeUiState +import com.google.jetpackcamera.ui.uistate.capture.FlipLensUiState +import com.google.jetpackcamera.ui.uistate.capture.FocusMeteringUiState +import com.google.jetpackcamera.ui.uistate.capture.HdrUiState +import com.google.jetpackcamera.ui.uistate.capture.ImageWellUiState +import com.google.jetpackcamera.ui.uistate.capture.StabilizationUiState +import com.google.jetpackcamera.ui.uistate.capture.ZoomControlUiState +import com.google.jetpackcamera.ui.uistate.capture.ZoomUiState +import com.google.jetpackcamera.ui.uistate.capture.compound.CaptureUiState +import com.google.jetpackcamera.ui.uistate.capture.compound.FocusedQuickSetting +import com.google.jetpackcamera.ui.uistate.capture.compound.PreviewDisplayUiState +import com.google.jetpackcamera.ui.uistate.capture.compound.QuickSettingsUiState +import com.google.jetpackcamera.ui.uistateadapter.capture.from +import com.google.jetpackcamera.ui.uistateadapter.capture.updateFrom +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +fun CaptureUiState.Companion.update( + captureUiState: MutableStateFlow, + cameraAppSettings: CameraAppSettings, + systemConstraints: CameraSystemConstraints, + cameraState: CameraState, + externalCaptureMode: ExternalCaptureMode, + debugSettings: DebugSettings, + cameraPropertiesJSON: String, + isQuickSettingsOpen: Boolean, + focusedQuickSetting: FocusedQuickSetting, + isDebugOverlayOpen: Boolean, + isRecordingLocked: Boolean, + zoomAnimationTarget: Float?, + debugHidingComponents: Boolean, + recentCapturedMedia: MediaDescriptor +) { + var flashModeUiState: FlashModeUiState + var focusMeteringUiState: FocusMeteringUiState + + val captureModeUiState = CaptureModeUiState.from( + systemConstraints, + cameraAppSettings, + externalCaptureMode + ) + val flipLensUiState = FlipLensUiState.from( + cameraAppSettings, + systemConstraints + ) + val aspectRatioUiState = AspectRatioUiState.from(cameraAppSettings) + val hdrUiState = HdrUiState.from( + cameraAppSettings, + systemConstraints, + externalCaptureMode + ) + captureUiState.update { old -> + when (old) { + is CaptureUiState.NotReady -> { + flashModeUiState = FlashModeUiState.from( + cameraAppSettings, + systemConstraints + ) + focusMeteringUiState = FocusMeteringUiState.from(cameraState) + // This is the first PreviewUiState.Ready. Create the initial + // PreviewUiState.Ready from defaults and initialize it below. + CaptureUiState.Ready() + } + + is CaptureUiState.Ready -> { + flashModeUiState = old.flashModeUiState.updateFrom( + cameraAppSettings = cameraAppSettings, + systemConstraints = systemConstraints, + cameraState = cameraState + ) + + focusMeteringUiState = old.focusMeteringUiState.updateFrom(cameraState) + // We have a previous `PreviewUiState.Ready`, return it here and + // update it below. + old + } + }.copy( + // Update or initialize PreviewUiState.Ready + externalCaptureMode = externalCaptureMode, + videoRecordingState = cameraState.videoRecordingState, + flipLensUiState = flipLensUiState, + aspectRatioUiState = aspectRatioUiState, + previewDisplayUiState = PreviewDisplayUiState(0, aspectRatioUiState), + quickSettingsUiState = QuickSettingsUiState.from( + captureModeUiState, + flashModeUiState, + flipLensUiState, + cameraAppSettings, + systemConstraints, + aspectRatioUiState, + hdrUiState, + isQuickSettingsOpen, + focusedQuickSetting, + externalCaptureMode + ), + sessionFirstFrameTimestamp = cameraState.sessionFirstFrameTimestamp, + debugUiState = DebugUiState.from( + systemConstraints, + cameraAppSettings, + cameraState, + isDebugOverlayOpen, + debugHidingComponents, + debugSettings, + cameraPropertiesJSON + ), + stabilizationUiState = StabilizationUiState.from( + cameraAppSettings, + cameraState + ), + flashModeUiState = flashModeUiState, + videoQuality = cameraState.videoQualityInfo.quality, + audioUiState = AudioUiState.from( + cameraAppSettings, + cameraState + ), + elapsedTimeUiState = ElapsedTimeUiState.from(cameraState), + captureButtonUiState = CaptureButtonUiState.from( + cameraAppSettings, + cameraState, + isRecordingLocked + ), + zoomUiState = ZoomUiState.from( + systemConstraints, + cameraAppSettings.cameraLensFacing, + cameraState + ), + zoomControlUiState = ZoomControlUiState.from( + zoomAnimationTarget, + systemConstraints, + cameraAppSettings, + cameraState + ), + captureModeToggleUiState = CaptureModeToggleUiState.from( + systemConstraints, + cameraAppSettings, + cameraState, + externalCaptureMode + ), + hdrUiState = hdrUiState, + focusMeteringUiState = focusMeteringUiState, + imageWellUiState = ImageWellUiState.from( + recentCapturedMedia, + cameraState.videoRecordingState + ) + + ) + } +} diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/QuickSettingsUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/QuickSettingsUiStateAdapter.kt new file mode 100644 index 000000000..fa2564cbf --- /dev/null +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/QuickSettingsUiStateAdapter.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 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.ui.uistateadapter.capture.compound + +import com.google.jetpackcamera.model.ExternalCaptureMode +import com.google.jetpackcamera.settings.model.CameraAppSettings +import com.google.jetpackcamera.settings.model.CameraSystemConstraints +import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState +import com.google.jetpackcamera.ui.uistate.capture.CaptureModeUiState +import com.google.jetpackcamera.ui.uistate.capture.ConcurrentCameraUiState +import com.google.jetpackcamera.ui.uistate.capture.FlashModeUiState +import com.google.jetpackcamera.ui.uistate.capture.FlipLensUiState +import com.google.jetpackcamera.ui.uistate.capture.HdrUiState +import com.google.jetpackcamera.ui.uistate.capture.StreamConfigUiState +import com.google.jetpackcamera.ui.uistate.capture.compound.FocusedQuickSetting +import com.google.jetpackcamera.ui.uistate.capture.compound.QuickSettingsUiState +import com.google.jetpackcamera.ui.uistateadapter.capture.from + +fun QuickSettingsUiState.Companion.from( + captureModeUiState: CaptureModeUiState, + flashModeUiState: FlashModeUiState, + flipLensUiState: FlipLensUiState, + cameraAppSettings: CameraAppSettings, + systemConstraints: CameraSystemConstraints, + aspectRatioUiState: AspectRatioUiState, + hdrUiState: HdrUiState, + quickSettingsIsOpen: Boolean, + focusedQuickSetting: FocusedQuickSetting, + externalCaptureMode: ExternalCaptureMode +): QuickSettingsUiState { + val streamConfigUiState = StreamConfigUiState.from(cameraAppSettings) + return QuickSettingsUiState.Available( + aspectRatioUiState = aspectRatioUiState, + captureModeUiState = captureModeUiState, + concurrentCameraUiState = ConcurrentCameraUiState.from( + cameraAppSettings, + systemConstraints, + externalCaptureMode, + captureModeUiState, + streamConfigUiState + ), + flashModeUiState = flashModeUiState, + flipLensUiState = flipLensUiState, + hdrUiState = hdrUiState, + streamConfigUiState = streamConfigUiState, + quickSettingsIsOpen = quickSettingsIsOpen, + focusedQuickSetting = focusedQuickSetting + ) +} From de5454d9584f5744d73a30c262668d49a128981e Mon Sep 17 00:00:00 2001 From: David Jia Date: Tue, 25 Nov 2025 15:23:22 -0800 Subject: [PATCH 2/2] style change to avoid var --- .../capture/compound/CaptureUiStateAdapter.kt | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt index 2b6a46c60..4d4d56775 100644 --- a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt @@ -61,9 +61,6 @@ fun CaptureUiState.Companion.update( debugHidingComponents: Boolean, recentCapturedMedia: MediaDescriptor ) { - var flashModeUiState: FlashModeUiState - var focusMeteringUiState: FocusMeteringUiState - val captureModeUiState = CaptureModeUiState.from( systemConstraints, cameraAppSettings, @@ -80,31 +77,28 @@ fun CaptureUiState.Companion.update( externalCaptureMode ) captureUiState.update { old -> - when (old) { - is CaptureUiState.NotReady -> { - flashModeUiState = FlashModeUiState.from( + val (baseState, flashModeUiState, focusMeteringUiState) = when (old) { + is CaptureUiState.NotReady -> Triple( + first = CaptureUiState.Ready(), + second = FlashModeUiState.from( cameraAppSettings, systemConstraints - ) - focusMeteringUiState = FocusMeteringUiState.from(cameraState) - // This is the first PreviewUiState.Ready. Create the initial - // PreviewUiState.Ready from defaults and initialize it below. - CaptureUiState.Ready() - } + ), + third = FocusMeteringUiState.from(cameraState) + ) - is CaptureUiState.Ready -> { - flashModeUiState = old.flashModeUiState.updateFrom( + is CaptureUiState.Ready -> Triple( + first = old, + second = old.flashModeUiState.updateFrom( cameraAppSettings = cameraAppSettings, systemConstraints = systemConstraints, cameraState = cameraState - ) + ), + third = old.focusMeteringUiState.updateFrom(cameraState) + ) + } - focusMeteringUiState = old.focusMeteringUiState.updateFrom(cameraState) - // We have a previous `PreviewUiState.Ready`, return it here and - // update it below. - old - } - }.copy( + baseState.copy( // Update or initialize PreviewUiState.Ready externalCaptureMode = externalCaptureMode, videoRecordingState = cameraState.videoRecordingState,