diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf1b4ed49..7fdc3fafe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,8 +44,8 @@ diff --git a/app/src/main/java/com/google/jetpackcamera/MainActivity.kt b/app/src/main/java/com/google/jetpackcamera/MainActivity.kt index 2e13a2e58..32c3002af 100644 --- a/app/src/main/java/com/google/jetpackcamera/MainActivity.kt +++ b/app/src/main/java/com/google/jetpackcamera/MainActivity.kt @@ -38,6 +38,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi @@ -102,6 +103,7 @@ class MainActivity : ComponentActivity() { } is Success -> { + val previewMode = remember { getPreviewMode() } // TODO(kimblebee@): add app setting to enable/disable dynamic color JetpackCameraTheme( darkTheme = isInDarkMode(uiState = uiState), @@ -117,7 +119,7 @@ class MainActivity : ComponentActivity() { ) { JcaApp( onPreviewViewModel = { previewViewModel = it }, - previewMode = getPreviewMode() + previewMode = previewMode ) } } 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 1b7657366..599189a2f 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 @@ -31,24 +31,27 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +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.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.jetpackcamera.feature.preview.ui.BlinkState import com.google.jetpackcamera.feature.preview.ui.CameraControlsOverlay import com.google.jetpackcamera.feature.preview.ui.PreviewDisplay import com.google.jetpackcamera.feature.preview.ui.ScreenFlashScreen +import com.google.jetpackcamera.feature.preview.ui.SmoothImmersiveRotationEffect import com.google.jetpackcamera.feature.preview.ui.TestableToast +import com.google.jetpackcamera.feature.preview.ui.rotatedLayout import com.google.jetpackcamera.feature.quicksettings.QuickSettingsScreenOverlay import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CaptureMode @@ -71,13 +74,16 @@ fun PreviewScreen( Log.d(TAG, "PreviewScreen") onPreviewViewModel(viewModel) - val previewUiState: PreviewUiState by viewModel.previewUiState.collectAsState() + // For this screen, force an immersive view with smooth rotation. + SmoothImmersiveRotationEffect(LocalContext.current) + + val previewUiState: PreviewUiState by viewModel.previewUiState.collectAsStateWithLifecycle() val screenFlashUiState: ScreenFlash.ScreenFlashUiState - by viewModel.screenFlash.screenFlashUiState.collectAsState() + by viewModel.screenFlash.screenFlashUiState.collectAsStateWithLifecycle() val surfaceRequest: SurfaceRequest? - by viewModel.surfaceRequest.collectAsState() + by viewModel.surfaceRequest.collectAsStateWithLifecycle() LifecycleStartEffect(Unit) { viewModel.startCamera() @@ -163,7 +169,7 @@ private fun ContentScreen( ) QuickSettingsScreenOverlay( - modifier = Modifier, + modifier = Modifier.rotatedLayout(), isOpen = previewUiState.quickSettingsIsOpen, toggleIsOpen = onToggleQuickSettings, currentCameraSettings = previewUiState.currentCameraSettings, @@ -175,6 +181,7 @@ private fun ContentScreen( ) // relative-grid style overlay on top of preview display CameraControlsOverlay( + modifier = Modifier.rotatedLayout(), previewUiState = previewUiState, onNavigateToSettings = onNavigateToSettings, previewMode = previewMode, 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 a5838696e..4e5419c10 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 @@ -29,12 +29,10 @@ import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Refresh @@ -56,10 +54,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.jetpackcamera.feature.preview.R @@ -67,6 +67,7 @@ import com.google.jetpackcamera.feature.preview.VideoRecordingState import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.Stabilization import com.google.jetpackcamera.settings.model.SupportedStabilizationMode +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope private const val TAG = "PreviewScreen" @@ -132,11 +133,12 @@ fun PreviewDisplay( val currentOnFlipCamera by rememberUpdatedState(onFlipCamera) surfaceRequest?.let { - BoxWithConstraints( - Modifier + Box( + modifier .testTag(PREVIEW_DISPLAY) .fillMaxSize() .background(Color.Black) + .wrapContentSize() .pointerInput(Unit) { detectTapGestures( onDoubleTap = { offset -> @@ -145,28 +147,46 @@ fun PreviewDisplay( currentOnFlipCamera() } ) - }, + } + .layout { measurable, constraints -> + val maxWidth = constraints.maxWidth.toFloat() + val maxHeight = constraints.maxHeight.toFloat() + val maxAspectRatio: Float = maxWidth / maxHeight + val aspectRatioFloat: Float = aspectRatio.ratio.toFloat() + + val correctAspectRation = if ( + (maxAspectRatio > 1 && aspectRatioFloat < 1) || + (maxAspectRatio < 1 && aspectRatioFloat > 1) + ) { + 1 / aspectRatioFloat + } else { + aspectRatioFloat + } + val shouldUseMaxWidth = maxAspectRatio <= correctAspectRation + val width = if (shouldUseMaxWidth) maxWidth else maxHeight * correctAspectRation + val height = + if (!shouldUseMaxWidth) maxHeight else maxWidth / correctAspectRation + + val placeable = measurable.measure( + Constraints.fixed( + width.roundToInt(), + height.roundToInt() + ) + ) + + layout(placeable.width, placeable.height) { + placeable.place(0, 0) + } + } + .transformable(state = transformableState) + .alpha(blinkState.alpha), contentAlignment = Alignment.Center ) { - val maxAspectRatio: Float = maxWidth / maxHeight - val aspectRatioFloat: Float = aspectRatio.ratio.toFloat() - val shouldUseMaxWidth = maxAspectRatio <= aspectRatioFloat - val width = if (shouldUseMaxWidth) maxWidth else maxHeight * aspectRatioFloat - val height = if (!shouldUseMaxWidth) maxHeight else maxWidth / aspectRatioFloat - Box( - modifier = Modifier - .width(width) - .height(height) - .transformable(state = transformableState) - .alpha(blinkState.alpha) - - ) { - CameraXViewfinder( - modifier = Modifier.fillMaxSize(), - surfaceRequest = it - ) - } + CameraXViewfinder( + modifier = Modifier.fillMaxSize(), + surfaceRequest = it + ) } } } diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/RotationUtil.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/RotationUtil.kt new file mode 100644 index 000000000..e1ee2b3b0 --- /dev/null +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/ui/RotationUtil.kt @@ -0,0 +1,118 @@ +/* + * 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.feature.preview.ui + +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.view.Surface +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT +import android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Constraints +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat + +/** + * As long as this composable is active, the window will go into immersive mode and prevents the + * rotation animation on configuration change. This will prevent the UI items from visually changing. + * + * When used in combination with a composable that renders the same UI in both landscape and portrait, + * it can create a smooth continuous feel between those two orientations. + */ +@Composable +fun SmoothImmersiveRotationEffect(context: Context) = DisposableEffect(context) { + var currentRotationAnimation: Int? = null + context.getActivity()?.window?.let { window -> + window.attributes = window.attributes.apply { + currentRotationAnimation = rotationAnimation + rotationAnimation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ROTATION_ANIMATION_SEAMLESS + } else { + ROTATION_ANIMATION_JUMPCUT + } + } + WindowCompat.getInsetsController(window, window.decorView).apply { + systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + hide(WindowInsetsCompat.Type.systemBars()) + } + } + onDispose { + context.getActivity()?.window?.let { window -> + if (currentRotationAnimation != null) { + window.attributes = window.attributes.apply { + rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE + } + } + WindowCompat.getInsetsController(window, window.decorView).apply { + show(WindowInsetsCompat.Type.systemBars()) + } + } + } +} + +/** + * Rotate a layout based on the current screen orientation. The UI will always be laid out in a way + * that width <= height, and rotated afterwards. + */ +@Composable +fun Modifier.rotatedLayout(): Modifier { + var currentOrientation by remember { mutableIntStateOf(Surface.ROTATION_0) } + val currentDegrees = currentOrientation * 90f + val newOrientation = LocalConfiguration.current.orientation + val display = LocalView.current.display + LaunchedEffect(newOrientation, display) { + val newRotation = display.rotation + if (currentOrientation != newRotation) { + currentOrientation = newRotation + } + } + return this then Modifier.fillMaxSize().layout { measurable, constraints -> + val height = maxOf(constraints.maxWidth, constraints.maxHeight) + val width = minOf(constraints.maxWidth, constraints.maxHeight) + val placeable = measurable.measure( + Constraints.fixed(width, height) + ) + layout(placeable.width, placeable.height) { + placeable.placeWithLayer(0, 0) { + if (constraints.maxWidth > constraints.maxHeight) { + rotationZ = -currentDegrees + } + } + } + } +} + +private fun Context.getActivity(): ComponentActivity? = when (this) { + is ComponentActivity -> this + is ContextWrapper -> baseContext.getActivity() + else -> null +} diff --git a/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/QuickSettingsScreen.kt b/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/QuickSettingsScreen.kt index fb34228a0..29f3be0e2 100644 --- a/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/QuickSettingsScreen.kt +++ b/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/QuickSettingsScreen.kt @@ -23,6 +23,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -45,7 +47,6 @@ import com.google.jetpackcamera.feature.quicksettings.ui.QuickFlipCamera import com.google.jetpackcamera.feature.quicksettings.ui.QuickSetCaptureMode import com.google.jetpackcamera.feature.quicksettings.ui.QuickSetFlash import com.google.jetpackcamera.feature.quicksettings.ui.QuickSetRatio -import com.google.jetpackcamera.feature.quicksettings.ui.QuickSettingsGrid import com.google.jetpackcamera.quicksettings.R import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CameraAppSettings @@ -128,6 +129,7 @@ private enum class IsExpandedQuickSetting { /** * The UI component for quick settings when it is expanded. */ +@OptIn(ExperimentalLayoutApi::class) @Composable private fun ExpandedQuickSettingsUi( currentCameraSettings: CameraAppSettings, @@ -151,48 +153,37 @@ private fun ExpandedQuickSettingsUi( // to change the order of display just move these lines of code above or below each other when (shouldShowQuickSetting) { IsExpandedQuickSetting.NONE -> { - val displayedQuickSettings: Array<@Composable () -> Unit> = - arrayOf( - { - QuickSetFlash( - modifier = Modifier.testTag(QUICK_SETTINGS_FLASH_BUTTON), - onClick = { f: FlashMode -> onFlashModeClick(f) }, - currentFlashMode = currentCameraSettings.flashMode - ) - }, - { - QuickFlipCamera( - modifier = Modifier.testTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON), - setLensFacing = { l: LensFacing -> onLensFaceClick(l) }, - currentLensFacing = currentCameraSettings.cameraLensFacing - ) - }, - { - QuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_BUTTON), - onClick = { - setVisibleQuickSetting( - IsExpandedQuickSetting.ASPECT_RATIO - ) - }, - ratio = currentCameraSettings.aspectRatio, - currentRatio = currentCameraSettings.aspectRatio + FlowRow { + QuickSetFlash( + modifier = Modifier.testTag(QUICK_SETTINGS_FLASH_BUTTON), + onClick = { f: FlashMode -> onFlashModeClick(f) }, + currentFlashMode = currentCameraSettings.flashMode + ) + QuickFlipCamera( + modifier = Modifier.testTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON), + setLensFacing = { l: LensFacing -> onLensFaceClick(l) }, + currentLensFacing = currentCameraSettings.cameraLensFacing + ) + QuickSetRatio( + modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_BUTTON), + onClick = { + setVisibleQuickSetting( + IsExpandedQuickSetting.ASPECT_RATIO ) }, - { - QuickSetCaptureMode( - modifier = Modifier.testTag(QUICK_SETTINGS_CAPTURE_MODE_BUTTON), - setCaptureMode = { c: CaptureMode -> onCaptureModeClick(c) }, - currentCaptureMode = currentCameraSettings.captureMode - ) - } + ratio = currentCameraSettings.aspectRatio, + currentRatio = currentCameraSettings.aspectRatio ) - QuickSettingsGrid(quickSettingsButtons = displayedQuickSettings) + QuickSetCaptureMode( + modifier = Modifier.testTag(QUICK_SETTINGS_CAPTURE_MODE_BUTTON), + setCaptureMode = { c: CaptureMode -> onCaptureModeClick(c) }, + currentCaptureMode = currentCameraSettings.captureMode + ) + } } // if a setting that can be expanded is selected, show it IsExpandedQuickSetting.ASPECT_RATIO -> { ExpandedQuickSetRatio( - setRatio = onAspectRatioClick, currentRatio = currentCameraSettings.aspectRatio ) diff --git a/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/ui/QuickSettingsComponents.kt b/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/ui/QuickSettingsComponents.kt index 631fb33da..7c65851a6 100644 --- a/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/ui/QuickSettingsComponents.kt +++ b/feature/quicksettings/src/main/java/com/google/jetpackcamera/feature/quicksettings/ui/QuickSettingsComponents.kt @@ -19,13 +19,12 @@ import androidx.annotation.DrawableRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -33,7 +32,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -52,45 +50,37 @@ import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.FlashMode import com.google.jetpackcamera.settings.model.LensFacing -import kotlin.math.min // completed components ready to go into preview screen +@OptIn(ExperimentalLayoutApi::class) @Composable fun ExpandedQuickSetRatio( setRatio: (aspectRatio: AspectRatio) -> Unit, currentRatio: AspectRatio, modifier: Modifier = Modifier ) { - val buttons: Array<@Composable () -> Unit> = - arrayOf( - { - QuickSetRatio( - onClick = { setRatio(AspectRatio.THREE_FOUR) }, - ratio = AspectRatio.THREE_FOUR, - currentRatio = currentRatio, - isHighlightEnabled = true - ) - }, - { - QuickSetRatio( - onClick = { setRatio(AspectRatio.NINE_SIXTEEN) }, - ratio = AspectRatio.NINE_SIXTEEN, - currentRatio = currentRatio, - isHighlightEnabled = true - ) - }, - { - QuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_1_1_BUTTON), - onClick = { setRatio(AspectRatio.ONE_ONE) }, - ratio = AspectRatio.ONE_ONE, - currentRatio = currentRatio, - isHighlightEnabled = true - ) - } + FlowRow(modifier) { + QuickSetRatio( + onClick = { setRatio(AspectRatio.THREE_FOUR) }, + ratio = AspectRatio.THREE_FOUR, + currentRatio = currentRatio, + isHighlightEnabled = true + ) + QuickSetRatio( + onClick = { setRatio(AspectRatio.NINE_SIXTEEN) }, + ratio = AspectRatio.NINE_SIXTEEN, + currentRatio = currentRatio, + isHighlightEnabled = true ) - ExpandedQuickSetting(modifier = modifier, quickSettingButtons = buttons) + QuickSetRatio( + modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_1_1_BUTTON), + onClick = { setRatio(AspectRatio.ONE_ONE) }, + ratio = AspectRatio.ONE_ONE, + currentRatio = currentRatio, + isHighlightEnabled = true + ) + } } @Composable @@ -276,77 +266,6 @@ fun QuickSettingUiItem( } } -/** - * Should you want to have an expanded view of a single quick setting - */ -@Composable -fun ExpandedQuickSetting( - modifier: Modifier = Modifier, - vararg quickSettingButtons: @Composable () -> Unit -) { - val expandedNumOfColumns = - min( - quickSettingButtons.size, - ( - ( - LocalConfiguration.current.screenWidthDp.dp - ( - dimensionResource( - id = R.dimen.quick_settings_ui_horizontal_padding - ) * 2 - ) - ) / - ( - dimensionResource(id = R.dimen.quick_settings_ui_item_icon_size) + - (dimensionResource(id = R.dimen.quick_settings_ui_item_padding) * 2) - ) - ).toInt() - ) - LazyVerticalGrid( - modifier = modifier.fillMaxWidth(), - columns = GridCells.Fixed(count = expandedNumOfColumns) - ) { - items(quickSettingButtons.size) { i -> - quickSettingButtons[i]() - } - } -} - -/** - * Algorithm to determine dimensions of QuickSettings Icon layout - */ -@Composable -fun QuickSettingsGrid( - modifier: Modifier = Modifier, - vararg quickSettingsButtons: @Composable () -> Unit -) { - val initialNumOfColumns = - min( - quickSettingsButtons.size, - ( - ( - LocalConfiguration.current.screenWidthDp.dp - ( - dimensionResource( - id = R.dimen.quick_settings_ui_horizontal_padding - ) * 2 - ) - ) / - ( - dimensionResource(id = R.dimen.quick_settings_ui_item_icon_size) + - (dimensionResource(id = R.dimen.quick_settings_ui_item_padding) * 2) - ) - ).toInt() - ) - - LazyVerticalGrid( - modifier = modifier.fillMaxWidth(), - columns = GridCells.Fixed(count = initialNumOfColumns) - ) { - items(quickSettingsButtons.size) { i -> - quickSettingsButtons[i]() - } - } -} - /** * The top bar indicators for quick settings items. */ diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 30a2251c4..bb979097d 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -90,6 +90,10 @@ dependencies { implementation(libs.compose.ui.tooling.preview) debugImplementation(libs.compose.ui.tooling) + // Compose - Lifecycle utilities + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.runtime.compose) + // Compose - Integration with ViewModels with Navigation and Hilt implementation(libs.hilt.navigation.compose) diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt index 63b39a90a..bc2e8013b 100644 --- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt @@ -22,12 +22,12 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.jetpackcamera.settings.model.AspectRatio import com.google.jetpackcamera.settings.model.CaptureMode import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS @@ -56,7 +56,7 @@ fun SettingsScreen( viewModel: SettingsViewModel = hiltViewModel(), onNavigateBack: () -> Unit ) { - val settingsUiState by viewModel.settingsUiState.collectAsState() + val settingsUiState by viewModel.settingsUiState.collectAsStateWithLifecycle() SettingsScreen( uiState = settingsUiState,