From 42fad7995f353de3587ffe233b423fc54122a492 Mon Sep 17 00:00:00 2001 From: Kimberly Crevecoeur Date: Thu, 18 Jan 2024 18:19:54 -0800 Subject: Preview screen layout refactor (#96) * small preview refactor to better organize components * convert preview overlay layer to a relative/grid layout style --- .../jetpackcamera/feature/preview/PreviewScreen.kt | 279 ++++++++++++--------- .../feature/preview/ui/PreviewScreenComponents.kt | 27 +- .../feature/quicksettings/QuickSettingsScreen.kt | 8 +- .../quicksettings/ui/QuickSettingsComponents.kt | 9 +- 4 files changed, 183 insertions(+), 140 deletions(-) diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt index 0f8fdcb..5aeb5c0 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 @@ -22,7 +22,6 @@ import android.util.Log import androidx.camera.core.Preview.SurfaceProvider import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row @@ -66,8 +65,9 @@ import com.google.jetpackcamera.feature.preview.ui.SettingsNavButton import com.google.jetpackcamera.feature.preview.ui.ShowTestableToast import com.google.jetpackcamera.feature.preview.ui.TestingButton import com.google.jetpackcamera.feature.preview.ui.ZoomScaleText -import com.google.jetpackcamera.feature.quicksettings.QuickSettingsScreen +import com.google.jetpackcamera.feature.quicksettings.QuickSettingsScreenOverlay import com.google.jetpackcamera.feature.quicksettings.ui.QuickSettingsIndicators +import com.google.jetpackcamera.feature.quicksettings.ui.ToggleQuickSettingsButton import com.google.jetpackcamera.settings.model.CaptureMode import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.awaitCancellation @@ -140,8 +140,21 @@ fun PreviewScreen( aspectRatio = previewUiState.currentCameraSettings.aspectRatio, deferredSurfaceProvider = deferredSurfaceProvider ) - // overlay - Box( + + QuickSettingsScreenOverlay( + modifier = Modifier, + isOpen = previewUiState.quickSettingsIsOpen, + toggleIsOpen = { viewModel.toggleQuickSettings() }, + currentCameraSettings = previewUiState.currentCameraSettings, + onLensFaceClick = viewModel::flipCamera, + onFlashModeClick = viewModel::setFlash, + onAspectRatioClick = { + viewModel.setAspectRatio(it) + } + // onTimerClick = {}/*TODO*/ + ) + // relative-grid style overlay on top of preview display + Column( modifier = Modifier .semantics { testTagsAsResourceId = true @@ -152,138 +165,170 @@ fun PreviewScreen( when (previewUiState.videoRecordingState) { VideoRecordingState.ACTIVE -> {} VideoRecordingState.INACTIVE -> { - QuickSettingsScreen( - modifier = Modifier - .align(Alignment.TopCenter), - isOpen = previewUiState.quickSettingsIsOpen, - toggleIsOpen = { viewModel.toggleQuickSettings() }, - currentCameraSettings = previewUiState.currentCameraSettings, - onLensFaceClick = viewModel::flipCamera, - onFlashModeClick = viewModel::setFlash, - onAspectRatioClick = { - viewModel.setAspectRatio(it) - } - // onTimerClick = {}/*TODO*/ - ) - + // 3-segmented row to keep quick settings button centered Row( modifier = Modifier - .align(Alignment.TopStart), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically + .fillMaxWidth() + .height(IntrinsicSize.Min) ) { - SettingsNavButton( + // row to left of quick settings button + Row( modifier = Modifier - .padding(12.dp), - onNavigateToSettings = onNavigateToSettings - ) - - QuickSettingsIndicators( - currentCameraSettings = previewUiState.currentCameraSettings, - onFlashModeClick = viewModel::setFlash - ) - } - - TestingButton( - modifier = Modifier - .testTag("ToggleCaptureMode") - .align(Alignment.TopEnd) - .padding(12.dp), - onClick = { viewModel.toggleCaptureMode() }, - text = stringResource( - when (previewUiState.currentCameraSettings.captureMode) { - CaptureMode.SINGLE_STREAM -> R.string.capture_mode_single_stream - CaptureMode.MULTI_STREAM -> R.string.capture_mode_multi_stream - } - ) - ) - } - } - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.BottomCenter) - ) { - if (zoomScaleShow) { - ZoomScaleText(zoomScale = zoomScale) - } - Row( - modifier = - Modifier - .fillMaxWidth() - .height(IntrinsicSize.Min) - ) { - when (previewUiState.videoRecordingState) { - VideoRecordingState.ACTIVE -> { - Spacer( + .weight(1f), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + // button to open default settings page + SettingsNavButton( modifier = Modifier - .fillMaxHeight() - .weight(1f) + .padding(12.dp), + onNavigateToSettings = onNavigateToSettings ) + if (!previewUiState.quickSettingsIsOpen) { + QuickSettingsIndicators( + currentCameraSettings = previewUiState.currentCameraSettings, + onFlashModeClick = viewModel::setFlash + ) + } } + // quick settings button + ToggleQuickSettingsButton( + toggleDropDown = { viewModel.toggleQuickSettings() }, + isOpen = previewUiState.quickSettingsIsOpen + ) - VideoRecordingState.INACTIVE -> { - FlipCameraButton( + // Row to right of quick settings + Row( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + TestingButton( modifier = Modifier - .weight(1f) - .fillMaxHeight(), - onClick = { viewModel.flipCamera() }, - // enable only when phone has front and rear camera - enabledCondition = - previewUiState.currentCameraSettings.isBackCameraAvailable && - previewUiState.currentCameraSettings.isFrontCameraAvailable + .testTag("ToggleCaptureMode"), + onClick = { viewModel.toggleCaptureMode() }, + text = stringResource( + when (previewUiState.currentCameraSettings.captureMode) { + CaptureMode.SINGLE_STREAM -> + R.string.capture_mode_single_stream + + CaptureMode.MULTI_STREAM -> + R.string.capture_mode_multi_stream + } + ) ) } } - val multipleEventsCutter = remember { MultipleEventsCutter() } - val context = LocalContext.current - /*todo: close quick settings on start record/image capture*/ - CaptureButton( - modifier = Modifier - .testTag(CAPTURE_BUTTON), - onClick = { - multipleEventsCutter.processEvent { - when (previewMode) { - is PreviewMode.StandardMode -> { - viewModel.captureImage() - } + } + } - is PreviewMode.ExternalImageCaptureMode -> { - viewModel.captureImage( - context.contentResolver, - previewMode.imageCaptureUri, - previewMode.onImageCapture - ) - } - } + // this component places a gap in the center of the column that will push out the top + // and bottom edges. This will also allow the addition of vertical button bars on the + // sides of the screen + Row( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + ) {} + + if (zoomScaleShow) { + ZoomScaleText(zoomScale = zoomScale) + } + + // 3-segmented row to keep capture button centered + Row( + modifier = + Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + ) { + when (previewUiState.videoRecordingState) { + // hide first segment while recording in progress + VideoRecordingState.ACTIVE -> { + Spacer( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + ) + } + // show first segment when not recording + VideoRecordingState.INACTIVE -> { + Row( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + if (!previewUiState.quickSettingsIsOpen) { + FlipCameraButton( + onClick = { viewModel.flipCamera() }, + // enable only when phone has front and rear camera + enabledCondition = + previewUiState.currentCameraSettings.isBackCameraAvailable && + previewUiState.currentCameraSettings + .isFrontCameraAvailable + ) } - }, - onLongPress = { viewModel.startVideoRecording() }, - onRelease = { viewModel.stopVideoRecording() }, - videoRecordingState = previewUiState.videoRecordingState - ) - /* spacer is a placeholder to maintain the proportionate location of this - row of UI elements. if you want to add another element, replace it with ONE - element. If you want to add multiple components, use a container - (Box, Row, Column, etc.) - */ - Spacer( - modifier = Modifier - .fillMaxHeight() - .weight(1f) - ) + } + } } - } - // displays toast when there is a message to show - if (previewUiState.toastMessageToShow != null) { - ShowTestableToast( + val multipleEventsCutter = remember { MultipleEventsCutter() } + val context = LocalContext.current + CaptureButton( modifier = Modifier - .testTag(previewUiState.toastMessageToShow!!.testTag), - toastMessage = previewUiState.toastMessageToShow!!, - onToastShown = viewModel::onToastShown + .testTag(CAPTURE_BUTTON), + onClick = { + multipleEventsCutter.processEvent { + when (previewMode) { + is PreviewMode.StandardMode -> { + viewModel.captureImage() + } + + is PreviewMode.ExternalImageCaptureMode -> { + viewModel.captureImage( + context.contentResolver, + previewMode.imageCaptureUri, + previewMode.onImageCapture + ) + } + } + } + if (previewUiState.quickSettingsIsOpen) { + viewModel.toggleQuickSettings() + } + }, + onLongPress = { + viewModel.startVideoRecording() + if (previewUiState.quickSettingsIsOpen) { + viewModel.toggleQuickSettings() + } + }, + onRelease = { viewModel.stopVideoRecording() }, + videoRecordingState = previewUiState.videoRecordingState ) + // You can replace this row so long as the weight of the component is 1f to + // ensure the capture button remains centered. + Row( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + ) { + /*TODO("Place other components here") */ + } } } + // displays toast when there is a message to show + if (previewUiState.toastMessageToShow != null) { + ShowTestableToast( + modifier = Modifier + .testTag(previewUiState.toastMessageToShow!!.testTag), + toastMessage = previewUiState.toastMessageToShow!!, + onToastShown = viewModel::onToastShown + ) + } // Screen flash overlay that stays on top of everything but invisible normally. This should // not be enabled based on whether screen flash is enabled because a previous image capture 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 6d16f1d..5c7b5ab 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 @@ -202,21 +202,18 @@ fun FlipCameraButton( enabledCondition: Boolean, onClick: () -> Unit ) { - Box(modifier = modifier) { - IconButton( - modifier = Modifier - .align(Alignment.Center) - .size(40.dp), - onClick = onClick, - enabled = enabledCondition - ) { - Icon( - imageVector = Icons.Filled.Refresh, - tint = Color.White, - contentDescription = stringResource(id = R.string.flip_camera_content_description), - modifier = Modifier.size(72.dp) - ) - } + IconButton( + modifier = modifier + .size(40.dp), + onClick = onClick, + enabled = enabledCondition + ) { + Icon( + imageVector = Icons.Filled.Refresh, + tint = Color.White, + contentDescription = stringResource(id = R.string.flip_camera_content_description), + modifier = Modifier.size(72.dp) + ) } } 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 6b0ba2f..e6b2ff9 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 @@ -38,7 +38,6 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId -import com.google.jetpackcamera.feature.quicksettings.ui.DropDownIcon import com.google.jetpackcamera.feature.quicksettings.ui.ExpandedQuickSetRatio import com.google.jetpackcamera.feature.quicksettings.ui.QuickFlipCamera import com.google.jetpackcamera.feature.quicksettings.ui.QuickSetFlash @@ -54,7 +53,7 @@ import com.google.jetpackcamera.settings.model.FlashMode */ @OptIn(ExperimentalComposeUiApi::class) @Composable -fun QuickSettingsScreen( +fun QuickSettingsScreenOverlay( modifier: Modifier = Modifier, currentCameraSettings: CameraAppSettings, isOpen: Boolean = false, @@ -115,11 +114,6 @@ fun QuickSettingsScreen( } else { shouldShowQuickSetting = IsExpandedQuickSetting.NONE } - DropDownIcon( - modifier = modifier, - toggleDropDown = toggleIsOpen, - isOpen = isOpen - ) } // enum representing which individual quick setting is currently expanded 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 d3bb28c..3242f35 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 @@ -164,8 +164,15 @@ fun QuickFlipCamera( ) } +/** + * Button to toggle quick settings + */ @Composable -fun DropDownIcon(modifier: Modifier = Modifier, toggleDropDown: () -> Unit, isOpen: Boolean) { +fun ToggleQuickSettingsButton( + modifier: Modifier = Modifier, + toggleDropDown: () -> Unit, + isOpen: Boolean +) { Row( modifier = modifier, horizontalArrangement = Arrangement.Center, -- cgit v1.2.3