diff --git a/app/src/androidTest/java/com/google/jetpackcamera/PreviewScreenTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/PreviewScreenTest.kt new file mode 100644 index 000000000..4bf197860 --- /dev/null +++ b/app/src/androidTest/java/com/google/jetpackcamera/PreviewScreenTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2026 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 + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createEmptyComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.GrantPermissionRule +import com.google.jetpackcamera.ui.components.capture.GRID_OVERLAY +import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_GRID_BUTTON +import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS +import com.google.jetpackcamera.utils.runMainActivityScenarioTest +import com.google.jetpackcamera.utils.visitQuickSettings +import com.google.jetpackcamera.utils.waitForCaptureButton +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PreviewScreenTest { + + @get:Rule + val permissionsRule: GrantPermissionRule = + GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray()) + + @get:Rule + val composeTestRule = createEmptyComposeRule() + + @Test + fun can_toggle_grid_on_and_off() = runMainActivityScenarioTest { + composeTestRule.waitForCaptureButton() + composeTestRule.visitQuickSettings(QUICK_SETTINGS_GRID_BUTTON) { + // Click the grid button to turn it on + onNodeWithTag(QUICK_SETTINGS_GRID_BUTTON).performClick() + } + + // Verify the grid is displayed + composeTestRule.onNodeWithTag(GRID_OVERLAY).assertIsDisplayed() + + composeTestRule.visitQuickSettings(QUICK_SETTINGS_GRID_BUTTON) { + // Click the grid button to turn it off + onNodeWithTag(QUICK_SETTINGS_GRID_BUTTON).performClick() + } + // Verify the grid is not displayed + composeTestRule.onNodeWithTag(GRID_OVERLAY).assertDoesNotExist() + } +} diff --git a/core/model/src/main/java/com/google/jetpackcamera/model/GridType.kt b/core/model/src/main/java/com/google/jetpackcamera/model/GridType.kt new file mode 100644 index 000000000..64529d698 --- /dev/null +++ b/core/model/src/main/java/com/google/jetpackcamera/model/GridType.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2026 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.model + +import com.google.jetpackcamera.model.proto.GridTypeProto + +enum class GridType { + NONE, + RULE_OF_THIRDS; + + companion object { + fun GridType.toProto(): GridTypeProto { + return when (this) { + NONE -> GridTypeProto.GRID_TYPE_PROTO_NONE + RULE_OF_THIRDS -> GridTypeProto.GRID_TYPE_PROTO_RULE_OF_THIRDS + } + } + + fun fromProto(proto: GridTypeProto): GridType { + return when (proto) { + GridTypeProto.GRID_TYPE_PROTO_NONE -> NONE + GridTypeProto.GRID_TYPE_PROTO_RULE_OF_THIRDS -> RULE_OF_THIRDS + else -> NONE + } + } + } +} diff --git a/core/model/src/main/proto/com/google/jetpackcamera/model/proto/grid_type.proto b/core/model/src/main/proto/com/google/jetpackcamera/model/proto/grid_type.proto new file mode 100644 index 000000000..3e466e98a --- /dev/null +++ b/core/model/src/main/proto/com/google/jetpackcamera/model/proto/grid_type.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2026 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. + */ + +syntax = "proto3"; + +option java_package = "com.google.jetpackcamera.model.proto"; +option java_multiple_files = true; + +enum GridTypeProto { + GRID_TYPE_PROTO_NONE = 0; + GRID_TYPE_PROTO_RULE_OF_THIRDS = 1; +} \ No newline at end of file diff --git a/data/settings/src/androidTest/java/com/google/jetpackcamera/settings/LocalSettingsRepositoryInstrumentedTest.kt b/data/settings/src/androidTest/java/com/google/jetpackcamera/settings/LocalSettingsRepositoryInstrumentedTest.kt index fafb38dc3..29c768b3f 100644 --- a/data/settings/src/androidTest/java/com/google/jetpackcamera/settings/LocalSettingsRepositoryInstrumentedTest.kt +++ b/data/settings/src/androidTest/java/com/google/jetpackcamera/settings/LocalSettingsRepositoryInstrumentedTest.kt @@ -26,6 +26,7 @@ import com.google.jetpackcamera.model.CaptureMode import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.settings.DataStoreModule.provideDataStore @@ -166,4 +167,16 @@ class LocalSettingsRepositoryInstrumentedTest { assertThat(initialImageFormat).isEqualTo(ImageOutputFormat.JPEG) assertThat(newImageFormat).isEqualTo(ImageOutputFormat.JPEG_ULTRA_HDR) } + + @Test + fun can_update_grid_type() = runTest { + val initialGridType = repository.getCurrentDefaultCameraAppSettings().gridType + repository.updateGridType(GridType.RULE_OF_THIRDS) + val newGridType = repository.getCurrentDefaultCameraAppSettings().gridType + + advanceUntilIdle() + assertThat(initialGridType).isNotEqualTo(newGridType) + assertThat(initialGridType).isEqualTo(GridType.NONE) + assertThat(newGridType).isEqualTo(GridType.RULE_OF_THIRDS) + } } diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt index 7034f9eba..7700d2fa7 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/LocalSettingsRepository.kt @@ -23,6 +23,9 @@ import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.DynamicRange.Companion.toProto import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType +import com.google.jetpackcamera.model.GridType.Companion.fromProto +import com.google.jetpackcamera.model.GridType.Companion.toProto import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.ImageOutputFormat.Companion.toProto import com.google.jetpackcamera.model.LensFacing @@ -84,6 +87,7 @@ class LocalSettingsRepository @Inject constructor( maxVideoDurationMillis = it.maxVideoDurationMillis, videoQuality = VideoQuality.fromProto(it.videoQuality), audioEnabled = it.audioEnabledStatus, + gridType = fromProto(it.gridType), captureMode = defaultCaptureModeOverride ) } @@ -220,4 +224,12 @@ class LocalSettingsRepository @Inject constructor( .build() } } + + override suspend fun updateGridType(gridType: GridType) { + jcaSettings.updateData { currentSettings -> + currentSettings.toBuilder() + .setGridType(gridType.toProto()) + .build() + } + } } diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt index 64381b54c..22c929649 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/SettingsRepository.kt @@ -19,6 +19,7 @@ import com.google.jetpackcamera.model.AspectRatio import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.model.LowLightBoostPriority @@ -62,4 +63,6 @@ interface SettingsRepository { suspend fun updateVideoQuality(videoQuality: VideoQuality) suspend fun updateAudioEnabled(isAudioEnabled: Boolean) + + suspend fun updateGridType(gridType: GridType) } 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 d3d388def..3b0268ce6 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 @@ -25,6 +25,7 @@ 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.GridType import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.model.LowLightBoostPriority @@ -56,6 +57,7 @@ data class CameraAppSettings( val concurrentCameraMode: ConcurrentCameraMode = ConcurrentCameraMode.OFF, val maxVideoDurationMillis: Long = UNLIMITED_VIDEO_DURATION, val lowLightBoostPriority: LowLightBoostPriority = LowLightBoostPriority.PRIORITIZE_AE_MODE, + val gridType: GridType = GridType.NONE, val debugSettings: DebugSettings = DebugSettings() ) diff --git a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt index 31b653d11..c0738306e 100644 --- a/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt +++ b/data/settings/src/main/java/com/google/jetpackcamera/settings/test/FakeSettingsRepository.kt @@ -19,6 +19,7 @@ import com.google.jetpackcamera.model.AspectRatio import com.google.jetpackcamera.model.DarkMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.model.LowLightBoostPriority @@ -98,4 +99,9 @@ object FakeSettingsRepository : SettingsRepository { currentCameraSettings = currentCameraSettings.copy(audioEnabled = isAudioEnabled) } + + override suspend fun updateGridType(gridType: GridType) { + currentCameraSettings = + currentCameraSettings.copy(gridType = gridType) + } } diff --git a/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto b/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto index eec01dd22..6dc821fef 100644 --- a/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto +++ b/data/settings/src/main/proto/com/google/jetpackcamera/settings/jca_settings.proto @@ -26,6 +26,7 @@ import "com/google/jetpackcamera/model/proto/lens_facing.proto"; import "com/google/jetpackcamera/model/proto/stabilization_mode.proto"; import "com/google/jetpackcamera/model/proto/video_quality.proto"; import "com/google/jetpackcamera/model/proto/low_light_boost_priority.proto"; +import "com/google/jetpackcamera/model/proto/grid_type.proto"; option java_package = "com.google.jetpackcamera.settings"; @@ -45,6 +46,7 @@ message JcaSettings { VideoQuality video_quality = 12; bool audio_enabled_status = 13; LowLightBoostPriority low_light_boost_priority = 14; + GridTypeProto grid_type = 15; // Non-camera app settings DarkMode dark_mode_status = 9; diff --git a/data/settings/src/test/java/com/google/jetpackcamera/settings/ProtoConversionTest.kt b/data/settings/src/test/java/com/google/jetpackcamera/settings/ProtoConversionTest.kt index 5ce8f2643..e371d9184 100644 --- a/data/settings/src/test/java/com/google/jetpackcamera/settings/ProtoConversionTest.kt +++ b/data/settings/src/test/java/com/google/jetpackcamera/settings/ProtoConversionTest.kt @@ -18,9 +18,12 @@ package com.google.jetpackcamera.settings import com.google.common.truth.Truth.assertThat import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.DynamicRange.Companion.toProto +import com.google.jetpackcamera.model.GridType +import com.google.jetpackcamera.model.GridType.Companion.toProto import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.ImageOutputFormat.Companion.toProto import com.google.jetpackcamera.model.proto.DynamicRange as DynamicRangeProto +import com.google.jetpackcamera.model.proto.GridTypeProto import com.google.jetpackcamera.model.proto.ImageOutputFormat as ImageOutputFormatProto import org.junit.Test import org.junit.runner.RunWith @@ -107,4 +110,34 @@ class ProtoConversionTest { assertThat(correctConversions(it)).isEqualTo(ImageOutputFormat.fromProto(it)) } } + + @Test + fun gridType_convertsToCorrectProto() { + val correctConversions = { gridType: GridType -> + when (gridType) { + GridType.NONE -> GridTypeProto.GRID_TYPE_PROTO_NONE + GridType.RULE_OF_THIRDS -> GridTypeProto.GRID_TYPE_PROTO_RULE_OF_THIRDS + } + } + + enumValues().forEach { + assertThat(correctConversions(it)).isEqualTo(it.toProto()) + } + } + + @Test + fun gridTypeProto_convertsToCorrectGridType() { + val correctConversions = { gridTypeProto: GridTypeProto -> + when (gridTypeProto) { + GridTypeProto.GRID_TYPE_PROTO_NONE, + GridTypeProto.UNRECOGNIZED + -> GridType.NONE + GridTypeProto.GRID_TYPE_PROTO_RULE_OF_THIRDS -> GridType.RULE_OF_THIRDS + } + } + + enumValues().forEach { + assertThat(correctConversions(it)).isEqualTo(GridType.fromProto(it)) + } + } } 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 27a5de914..e9ce8ca3e 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 @@ -72,6 +72,7 @@ import com.google.jetpackcamera.model.ConcurrentCameraMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.ExternalCaptureMode import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageCaptureEvent import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing @@ -335,6 +336,7 @@ fun PreviewScreen( onStartVideoRecording = viewModel::startVideoRecording, onStopVideoRecording = viewModel::stopVideoRecording, onLockVideoRecording = viewModel::setLockedRecording, + onUpdateGridType = viewModel::updateGridType, onRequestWindowColorMode = onRequestWindowColorMode, onSnackBarResult = viewModel::onSnackBarResult, onNavigatePostCapture = onNavigateToPostCapture, @@ -391,6 +393,7 @@ private fun ContentScreen( onStartVideoRecording: () -> Unit = {}, onStopVideoRecording: () -> Unit = {}, onLockVideoRecording: (Boolean) -> Unit = {}, + onUpdateGridType: (GridType) -> Unit = {}, onRequestWindowColorMode: (Int) -> Unit = {}, onSnackBarResult: (String) -> Unit = {}, onNavigatePostCapture: () -> Unit = {}, @@ -417,8 +420,11 @@ private fun ContentScreen( } } + val debugHidingComponents = + (debugUiState as? DebugUiState.Enabled)?.debugHidingComponents == true LayoutWrapper( modifier = modifier, + debugHidingComponents = debugHidingComponents, hdrIndicator = { HdrIndicator(modifier = it, hdrUiState = captureUiState.hdrUiState) }, flashModeIndicator = { FlashModeIndicator( @@ -447,7 +453,8 @@ private fun ContentScreen( onScaleZoom = { onScaleZoom(it, LensToZoom.PRIMARY) }, surfaceRequest = surfaceRequest, onRequestWindowColorMode = onRequestWindowColorMode, - focusMeteringUiState = captureUiState.focusMeteringUiState + focusMeteringUiState = captureUiState.focusMeteringUiState, + debugHidingComponents = debugHidingComponents ) }, captureButton = { @@ -567,6 +574,7 @@ private fun ContentScreen( onImageOutputFormatClick = onChangeImageFormat, onConcurrentCameraModeClick = onChangeConcurrentCameraMode, onCaptureModeClick = onSetCaptureMode, + onGridClick = onUpdateGridType, onNavigateToSettings = { onToggleQuickSettings() onNavigateToSettings() @@ -656,6 +664,7 @@ private fun LoadingScreen(modifier: Modifier = Modifier) { @Composable private fun LayoutWrapper( modifier: Modifier = Modifier, + debugHidingComponents: Boolean, viewfinder: @Composable (modifier: Modifier) -> Unit, captureButton: @Composable (modifier: Modifier) -> Unit, flipCameraButton: @Composable (modifier: Modifier) -> Unit, 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 1d6e7b463..472856fce 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 @@ -46,6 +46,7 @@ import com.google.jetpackcamera.model.DeviceRotation import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.ExternalCaptureMode import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageCaptureEvent import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.IntProgress @@ -182,6 +183,9 @@ class PreviewViewModel @Inject constructor( init { viewModelScope.launch { + val initialGridType = + settingsRepository.getCurrentDefaultCameraAppSettings().gridType + trackedCaptureUiState.update { it.copy(gridType = initialGridType) } launch { var oldCameraAppSettings: CameraAppSettings? = null settingsRepository.defaultCameraAppSettings @@ -694,4 +698,11 @@ class PreviewViewModel @Inject constructor( cameraSystem.setDeviceRotation(deviceRotation) } } + + fun updateGridType(gridType: GridType) { + trackedCaptureUiState.update { it.copy(gridType = gridType) } + viewModelScope.launch { + settingsRepository.updateGridType(gridType) + } + } } diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt index fb512dd7c..8bd3e18bd 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt @@ -41,6 +41,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut +import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTapGestures @@ -92,6 +93,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.graphics.vector.rememberVectorPainter @@ -101,11 +103,13 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import com.google.jetpackcamera.core.camera.VideoRecordingState import com.google.jetpackcamera.model.CaptureMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.StabilizationMode import com.google.jetpackcamera.model.VideoQuality import com.google.jetpackcamera.ui.uistate.DisableRationale @@ -123,11 +127,14 @@ import com.google.jetpackcamera.ui.uistate.capture.FocusMeteringUiState import com.google.jetpackcamera.ui.uistate.capture.StabilizationUiState import com.google.jetpackcamera.ui.uistate.capture.compound.PreviewDisplayUiState import kotlin.time.Duration.Companion.nanoseconds +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.withContext private const val TAG = "PreviewScreen" private const val BLINK_TIME = 100L @@ -471,6 +478,7 @@ fun PreviewDisplay( onRequestWindowColorMode: (Int) -> Unit, surfaceRequest: SurfaceRequest?, focusMeteringUiState: FocusMeteringUiState, + debugHidingComponents: Boolean, modifier: Modifier = Modifier ) { if (previewDisplayUiState.aspectRatioUiState !is AspectRatioUiState.Available) { @@ -482,50 +490,51 @@ fun PreviewDisplay( } ) - surfaceRequest?.let { - BoxWithConstraints( - modifier - .testTag(PREVIEW_DISPLAY) - .fillMaxSize() - .background(Color.Black), - contentAlignment = Alignment.TopCenter - ) { - val aspectRatio = ( - previewDisplayUiState.aspectRatioUiState as - AspectRatioUiState.Available - ).selectedAspectRatio - val maxAspectRatio: Float = maxWidth / maxHeight - val aspectRatioFloat: Float = aspectRatio.toFloat() - val shouldUseMaxWidth = maxAspectRatio <= aspectRatioFloat - val width = if (shouldUseMaxWidth) maxWidth else maxHeight * aspectRatioFloat - val height = if (!shouldUseMaxWidth) maxHeight else maxWidth / aspectRatioFloat - var imageVisible by remember { mutableStateOf(true) } - - val imageAlpha: Float by animateFloatAsState( - targetValue = if (imageVisible) 1f else 0f, - animationSpec = tween( - durationMillis = (BLINK_TIME / 2).toInt(), - easing = LinearEasing - ), - label = "" - ) + BoxWithConstraints( + modifier + .testTag(PREVIEW_DISPLAY) + .fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + val aspectRatio = ( + previewDisplayUiState.aspectRatioUiState as + AspectRatioUiState.Available + ).selectedAspectRatio + val maxAspectRatio: Float = maxWidth / maxHeight + val aspectRatioFloat: Float = aspectRatio.toFloat() + val shouldUseMaxWidth = maxAspectRatio <= aspectRatioFloat + val width = if (shouldUseMaxWidth) maxWidth else maxHeight * aspectRatioFloat + val height = if (!shouldUseMaxWidth) maxHeight else maxWidth / aspectRatioFloat + var imageVisible by remember { mutableStateOf(true) } - LaunchedEffect(previewDisplayUiState.lastBlinkTimeStamp) { - if (previewDisplayUiState.lastBlinkTimeStamp != 0L) { - imageVisible = false - delay(BLINK_TIME) - imageVisible = true - } + val imageAlpha: Float by animateFloatAsState( + targetValue = if (imageVisible) 1f else 0f, + animationSpec = tween( + durationMillis = (BLINK_TIME / 2).toInt(), + easing = LinearEasing + ), + label = "" + ) + + LaunchedEffect(previewDisplayUiState.lastBlinkTimeStamp) { + if (previewDisplayUiState.lastBlinkTimeStamp != 0L) { + imageVisible = false + delay(BLINK_TIME) + imageVisible = true } + } - Box( - modifier = Modifier - .width(width) - .height(height) - .transformable(state = transformableState) - .alpha(imageAlpha) - .clip(RoundedCornerShape(16.dp)) - ) { + Box( + modifier = Modifier + .width(width) + .height(height) + .transformable(state = transformableState) + .alpha(imageAlpha) + .clip(RoundedCornerShape(16.dp)) + .background(Color.Black) + ) { + val coordinateTransformer = remember { MutableCoordinateTransformer() } + if (surfaceRequest != null) { val implementationMode = when { Build.VERSION.SDK_INT > 24 -> ImplementationMode.EXTERNAL else -> ImplementationMode.EMBEDDED @@ -536,8 +545,6 @@ fun PreviewDisplay( implementationMode = implementationMode, onRequestWindowColorMode = onRequestWindowColorMode ) - - val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( modifier = Modifier .fillMaxSize() @@ -561,15 +568,24 @@ fun PreviewDisplay( } ) }, - surfaceRequest = it, + surfaceRequest = surfaceRequest, implementationMode = implementationMode, coordinateTransformer = coordinateTransformer ) - FocusMeteringIndicator( - focusMeteringUiState = focusMeteringUiState, - coordinateTransformer = coordinateTransformer + } else { + // for preview + Box(modifier = Modifier.fillMaxSize().background(Color.Black)) + } + if (!debugHidingComponents) { + RuleOfThirdsGrid( + gridType = previewDisplayUiState.gridType, + modifier = Modifier.fillMaxSize() ) } + FocusMeteringIndicator( + focusMeteringUiState = focusMeteringUiState, + coordinateTransformer = coordinateTransformer + ) } } } @@ -916,3 +932,129 @@ private fun FocusMeteringIndicator( } } } + +/** + * A composable that draws a 3x3 grid (Rule of Thirds) overlay. + */ +@Composable +fun RuleOfThirdsGrid(modifier: Modifier = Modifier, gridType: GridType) { + val alpha = remember { Animatable(0f) } + + val currentGridType by rememberUpdatedState(gridType) + + // This LaunchedEffect creates a long-lived coroutine that won't be canceled when the + // showGrid state changes. This is important because we want the fade-out animation to + // always complete, even if the state changes quickly. The snapshotFlow and collectLatest + // will handle the cancellation and restart of the animation logic when showGrid changes, + // but the delay in the NonCancellable block will always complete. + LaunchedEffect(Unit) { + snapshotFlow { currentGridType } + .collectLatest { show -> + if (show == GridType.RULE_OF_THIRDS) { + alpha.animateTo(0.5f) + } else { + withContext(NonCancellable) { + alpha.animateTo(0f) + delay(200) + } + } + } + } + if (alpha.value != 0f) { + Canvas( + modifier = modifier + .fillMaxSize() + .alpha(alpha.value) + .testTag(GRID_OVERLAY) + ) { + val strokeWidth = 1.dp.toPx() + val thirdWidth = size.width / 3 + val thirdHeight = size.height / 3 + val lineColor = Color.White + + // Vertical lines + drawLine( + color = lineColor, + start = Offset(thirdWidth, 0f), + end = Offset(thirdWidth, size.height), + strokeWidth = strokeWidth + ) + drawLine( + color = lineColor, + start = Offset(thirdWidth * 2, 0f), + end = Offset(thirdWidth * 2, size.height), + strokeWidth = strokeWidth + ) + + // Horizontal lines + drawLine( + color = lineColor, + start = Offset(0f, thirdHeight), + end = Offset(size.width, thirdHeight), + strokeWidth = strokeWidth + ) + drawLine( + color = lineColor, + start = Offset(0f, thirdHeight * 2), + end = Offset(size.width, thirdHeight * 2), + strokeWidth = strokeWidth + ) + } + } +} + +@Preview +@Composable +private fun RuleOfThirdsGridPreview() { + Box( + modifier = Modifier + .background(Color.Black) + .fillMaxSize() + ) { + RuleOfThirdsGrid(gridType = GridType.RULE_OF_THIRDS) + } +} + +@Preview +@Composable +private fun PreviewDisplayGridAnimation() { + var gridType by remember { mutableStateOf(GridType.RULE_OF_THIRDS) } + + LaunchedEffect(Unit) { + while (true) { + delay(1000) + gridType = if (gridType == GridType.RULE_OF_THIRDS) { + GridType.NONE + } else { + GridType.RULE_OF_THIRDS + } + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.DarkGray) + ) { + PreviewDisplay( + previewDisplayUiState = PreviewDisplayUiState( + aspectRatioUiState = AspectRatioUiState.Available( + selectedAspectRatio = com.google.jetpackcamera.model.AspectRatio.NINE_SIXTEEN, + availableAspectRatios = listOf( + SingleSelectableUiState.SelectableUi( + com.google.jetpackcamera.model.AspectRatio.NINE_SIXTEEN + ) + ) + ), + gridType = gridType + ), + onTapToFocus = { _, _ -> }, + onFlipCamera = { }, + onScaleZoom = { }, + onRequestWindowColorMode = { }, + surfaceRequest = null, + focusMeteringUiState = FocusMeteringUiState.Unspecified, + debugHidingComponents = false + ) + } +} diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt index 9bf92ba3c..da888f596 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/TestTags.kt @@ -18,6 +18,7 @@ package com.google.jetpackcamera.ui.components.capture const val CAPTURE_BUTTON = "CaptureButton" const val CAPTURE_MODE_TOGGLE_BUTTON = "CaptureModeToggleButton" const val FLIP_CAMERA_BUTTON = "FlipCameraButton" +const val GRID_OVERLAY = "GridOverlay" const val IMAGE_CAPTURE_SUCCESS_TAG = "ImageCaptureSuccessTag" const val IMAGE_CAPTURE_FAILURE_TAG = "ImageCaptureFailureTag" const val IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG = "ImageCaptureExternalUnsupportedTag" @@ -95,3 +96,4 @@ const val BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_VIDEO_ONLY = "quick_settings_focused_capture_mode_btn_option_video_only" const val BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_IMAGE_ONLY = "quick_settings_focused_capture_mode_btn_option_image_only" +const val QUICK_SETTINGS_GRID_BUTTON = "btn_quick_settings_grid" diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt index c182c4e5e..ffd4f2567 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt @@ -30,6 +30,7 @@ import com.google.jetpackcamera.model.CaptureMode import com.google.jetpackcamera.model.ConcurrentCameraMode import com.google.jetpackcamera.model.DynamicRange import com.google.jetpackcamera.model.FlashMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.model.ImageOutputFormat import com.google.jetpackcamera.model.LensFacing import com.google.jetpackcamera.model.StreamConfig @@ -46,6 +47,7 @@ import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickFlip import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickNavSettings import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSetConcurrentCamera import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSetFlash +import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSetGrid import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSetHdr import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSetStreamConfig import com.google.jetpackcamera.ui.components.capture.quicksettings.ui.QuickSettingsBottomSheet as BottomSheetComponent @@ -82,7 +84,8 @@ fun QuickSettingsBottomSheet( onDynamicRangeClick: (dynamicRange: DynamicRange) -> Unit, onImageOutputFormatClick: (imageOutputFormat: ImageOutputFormat) -> Unit, onConcurrentCameraModeClick: (concurrentCameraMode: ConcurrentCameraMode) -> Unit, - onCaptureModeClick: (CaptureMode) -> Unit + onCaptureModeClick: (CaptureMode) -> Unit, + onGridClick: (GridType) -> Unit ) { if (quickSettingsUiState is QuickSettingsUiState.Available) { val onUnFocus = { onSetFocusedSetting(FocusedQuickSetting.NONE) } @@ -181,6 +184,18 @@ fun QuickSettingsBottomSheet( ) } + add { + QuickSetGrid( + onClick = { + when (quickSettingsUiState.gridType) { + GridType.NONE -> onGridClick(GridType.RULE_OF_THIRDS) + GridType.RULE_OF_THIRDS -> onGridClick(GridType.NONE) + } + }, + gridType = quickSettingsUiState.gridType + ) + } + add { QuickNavSettings( modifier = Modifier @@ -266,6 +281,7 @@ fun ExpandedQuickSettingsUiPreview() { ), isActive = false ), + gridType = GridType.NONE, quickSettingsIsOpen = true ), onLensFaceClick = { }, @@ -278,6 +294,7 @@ fun ExpandedQuickSettingsUiPreview() { toggleQuickSettings = { }, onNavigateToSettings = { }, onCaptureModeClick = { }, + onGridClick = {}, onSetFocusedSetting = {} ) } @@ -337,6 +354,7 @@ fun ExpandedQuickSettingsUiPreview_WithHdr() { ), isActive = false ), + gridType = GridType.NONE, quickSettingsIsOpen = true ), onLensFaceClick = { }, @@ -349,6 +367,7 @@ fun ExpandedQuickSettingsUiPreview_WithHdr() { toggleQuickSettings = { }, onNavigateToSettings = { }, onCaptureModeClick = { }, + onGridClick = {}, onSetFocusedSetting = {} ) } diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSetGrid.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSetGrid.kt new file mode 100644 index 000000000..f31ca76a4 --- /dev/null +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSetGrid.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 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.components.capture.quicksettings.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import com.google.jetpackcamera.model.GridType +import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_GRID_BUTTON + +@Composable +fun QuickSetGrid(modifier: Modifier = Modifier, onClick: (GridType) -> Unit, gridType: GridType) { + QuickSettingsGrid( + modifier = modifier.testTag(QUICK_SETTINGS_GRID_BUTTON), + onClick = { + when (gridType) { + GridType.NONE -> onClick(GridType.RULE_OF_THIRDS) + GridType.RULE_OF_THIRDS -> onClick(GridType.NONE) + } + }, + isGridOn = gridType != GridType.NONE + ) +} diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt index 77b625933..fe1b4049a 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Grid3x3 import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -111,6 +112,23 @@ import com.google.jetpackcamera.ui.uistate.capture.HdrUiState import com.google.jetpackcamera.ui.uistate.capture.StreamConfigUiState import kotlin.math.min +@Composable +fun QuickSettingsGrid(modifier: Modifier = Modifier, onClick: () -> Unit, isGridOn: Boolean) { + val painter = if (isGridOn) { + rememberVectorPainter(image = Icons.Default.Grid3x3) + } else { + painterResource(id = R.drawable.grid_3x3_off) + } + QuickSettingToggleButton( + modifier = modifier, + text = stringResource(id = R.string.quick_settings_grid_text), + accessibilityText = stringResource(id = R.string.quick_settings_grid_description), + onClick = onClick, + isHighlighted = isGridOn, + painter = painter + ) +} + @Composable fun QuickSetRatio( onClick: () -> Unit, diff --git a/ui/components/capture/src/main/res/drawable/grid_3x3_off.xml b/ui/components/capture/src/main/res/drawable/grid_3x3_off.xml new file mode 100644 index 000000000..181d4c9c2 --- /dev/null +++ b/ui/components/capture/src/main/res/drawable/grid_3x3_off.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/ui/components/capture/src/main/res/values/strings.xml b/ui/components/capture/src/main/res/values/strings.xml index b551cfec0..11dd7505b 100644 --- a/ui/components/capture/src/main/res/values/strings.xml +++ b/ui/components/capture/src/main/res/values/strings.xml @@ -139,6 +139,10 @@ More Settings icon + + Grid + Grid View + View recently saved media \ No newline at end of file diff --git a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/TrackedCaptureUiState.kt b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/TrackedCaptureUiState.kt index 227b954d7..dddb677e6 100644 --- a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/TrackedCaptureUiState.kt +++ b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/TrackedCaptureUiState.kt @@ -16,6 +16,7 @@ package com.google.jetpackcamera.ui.uistate.capture import com.google.jetpackcamera.data.media.MediaDescriptor +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.ui.uistate.capture.compound.FocusedQuickSetting /** @@ -35,5 +36,6 @@ data class TrackedCaptureUiState( val zoomAnimationTarget: Float? = null, val debugHidingComponents: Boolean = false, val recentCapturedMedia: MediaDescriptor = MediaDescriptor.None, - val lastBlinkTimeStamp: Long = 0 + val lastBlinkTimeStamp: Long = 0, + val gridType: GridType = GridType.NONE ) diff --git a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/PreviewDisplayUiState.kt b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/PreviewDisplayUiState.kt index d6ed953c4..a8ef0634d 100644 --- a/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/PreviewDisplayUiState.kt +++ b/ui/uistate/capture/src/main/java/com/google/jetpackcamera/ui/uistate/capture/compound/PreviewDisplayUiState.kt @@ -15,6 +15,7 @@ */ package com.google.jetpackcamera.ui.uistate.capture.compound +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState /** @@ -22,8 +23,10 @@ import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState * * @param lastBlinkTimeStamp The timestamp of the most recent capture blink animation. * @param aspectRatioUiState The UI state for the aspect ratio of the preview. + * @param gridType The type of grid to display. */ data class PreviewDisplayUiState( val lastBlinkTimeStamp: Long = 0, - val aspectRatioUiState: AspectRatioUiState + val aspectRatioUiState: AspectRatioUiState, + val gridType: GridType = GridType.NONE ) 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 49cab445f..1aa5dfcf9 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 @@ -15,6 +15,7 @@ */ package com.google.jetpackcamera.ui.uistate.capture.compound +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState import com.google.jetpackcamera.ui.uistate.capture.CaptureModeUiState import com.google.jetpackcamera.ui.uistate.capture.ConcurrentCameraUiState @@ -55,6 +56,7 @@ sealed interface QuickSettingsUiState { val concurrentCameraUiState: ConcurrentCameraUiState, val flashModeUiState: FlashModeUiState, val flipLensUiState: FlipLensUiState, + val gridType: GridType, val hdrUiState: HdrUiState, val streamConfigUiState: StreamConfigUiState, val quickSettingsIsOpen: Boolean = false, diff --git a/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/GridUiStateAdapter.kt b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/GridUiStateAdapter.kt new file mode 100644 index 000000000..adf802f7f --- /dev/null +++ b/ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/GridUiStateAdapter.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2026 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 + +import com.google.jetpackcamera.model.GridType +import com.google.jetpackcamera.ui.uistate.capture.TrackedCaptureUiState + +/** + * Creates a [GridType] from the given [TrackedCaptureUiState]. + * + * This function acts as an adapter to extract the grid-related UI state. + * + * @param trackedCaptureUiState The UI state holder. + * @return The current [GridType]. + */ +fun GridType.Companion.from(trackedCaptureUiState: TrackedCaptureUiState): GridType { + return trackedCaptureUiState.gridType +} 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 eabc94353..993c9b307 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 @@ -17,6 +17,7 @@ package com.google.jetpackcamera.ui.uistateadapter.capture.compound import com.google.jetpackcamera.core.camera.CameraSystem import com.google.jetpackcamera.model.ExternalCaptureMode +import com.google.jetpackcamera.model.GridType import com.google.jetpackcamera.settings.ConstraintsRepository import com.google.jetpackcamera.ui.uistate.capture.AspectRatioUiState import com.google.jetpackcamera.ui.uistate.capture.AudioUiState @@ -111,22 +112,26 @@ fun captureUiState( flipLensUiState = flipLensUiState, aspectRatioUiState = aspectRatioUiState, previewDisplayUiState = PreviewDisplayUiState( - trackedUiState.lastBlinkTimeStamp, - aspectRatioUiState + lastBlinkTimeStamp = trackedUiState.lastBlinkTimeStamp, + aspectRatioUiState = aspectRatioUiState, + gridType = if (cameraState.isCameraRunning) { + trackedUiState.gridType + } else { + GridType.NONE + } ), // TODO: add updateFrom() for all ui states to prevent re-updating if // values are the same quickSettingsUiState = QuickSettingsUiState.from( - captureModeUiState, - flashModeUiState, - flipLensUiState, - cameraAppSettings, - systemConstraints, - aspectRatioUiState, - hdrUiState, - trackedUiState.isQuickSettingsOpen, - trackedUiState.focusedQuickSetting, - externalCaptureMode + captureModeUiState = captureModeUiState, + flashModeUiState = flashModeUiState, + flipLensUiState = flipLensUiState, + cameraAppSettings = cameraAppSettings, + systemConstraints = systemConstraints, + aspectRatioUiState = aspectRatioUiState, + hdrUiState = hdrUiState, + trackedCaptureUiState = trackedUiState, + externalCaptureMode = externalCaptureMode ), sessionFirstFrameTimestamp = cameraState.sessionFirstFrameTimestamp, stabilizationUiState = StabilizationUiState.from( 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 index 2563adac4..846b89127 100644 --- 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 @@ -25,7 +25,7 @@ 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.TrackedCaptureUiState import com.google.jetpackcamera.ui.uistate.capture.compound.QuickSettingsUiState import com.google.jetpackcamera.ui.uistateadapter.capture.from @@ -42,8 +42,7 @@ import com.google.jetpackcamera.ui.uistateadapter.capture.from * @param systemConstraints The constraints of the camera system. * @param aspectRatioUiState The UI state for the aspect ratio setting. * @param hdrUiState The UI state for the HDR setting. - * @param quickSettingsIsOpen Indicates whether the quick settings panel is open. - * @param focusedQuickSetting The currently focused quick setting, if any. + * @param trackedCaptureUiState The UI state for the all the quick settings. * @param externalCaptureMode The external capture mode, if any. * @return A [QuickSettingsUiState.Available] instance containing the consolidated states. */ @@ -55,8 +54,7 @@ fun QuickSettingsUiState.Companion.from( systemConstraints: CameraSystemConstraints, aspectRatioUiState: AspectRatioUiState, hdrUiState: HdrUiState, - quickSettingsIsOpen: Boolean, - focusedQuickSetting: FocusedQuickSetting, + trackedCaptureUiState: TrackedCaptureUiState, externalCaptureMode: ExternalCaptureMode ): QuickSettingsUiState { val streamConfigUiState = StreamConfigUiState.from(cameraAppSettings) @@ -72,9 +70,10 @@ fun QuickSettingsUiState.Companion.from( ), flashModeUiState = flashModeUiState, flipLensUiState = flipLensUiState, + gridType = trackedCaptureUiState.gridType, hdrUiState = hdrUiState, streamConfigUiState = streamConfigUiState, - quickSettingsIsOpen = quickSettingsIsOpen, - focusedQuickSetting = focusedQuickSetting + quickSettingsIsOpen = trackedCaptureUiState.isQuickSettingsOpen, + focusedQuickSetting = trackedCaptureUiState.focusedQuickSetting ) }