• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.jetpackcamera.feature.preview.quicksettings
17 
18 import androidx.activity.compose.BackHandler
19 import androidx.compose.animation.AnimatedVisibility
20 import androidx.compose.animation.fadeIn
21 import androidx.compose.animation.fadeOut
22 import androidx.compose.animation.slideInVertically
23 import androidx.compose.animation.slideOutVertically
24 import androidx.compose.foundation.background
25 import androidx.compose.foundation.clickable
26 import androidx.compose.foundation.interaction.MutableInteractionSource
27 import androidx.compose.foundation.layout.Arrangement
28 import androidx.compose.foundation.layout.Column
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.foundation.layout.padding
31 import androidx.compose.material3.MaterialTheme
32 import androidx.compose.runtime.Composable
33 import androidx.compose.runtime.getValue
34 import androidx.compose.runtime.mutableStateOf
35 import androidx.compose.runtime.remember
36 import androidx.compose.runtime.setValue
37 import androidx.compose.ui.Alignment
38 import androidx.compose.ui.Modifier
39 import androidx.compose.ui.graphics.Color
40 import androidx.compose.ui.platform.testTag
41 import androidx.compose.ui.res.dimensionResource
42 import androidx.compose.ui.tooling.preview.Preview
43 import com.google.jetpackcamera.core.camera.VideoRecordingState
44 import com.google.jetpackcamera.feature.preview.CaptureModeToggleUiState
45 import com.google.jetpackcamera.feature.preview.DEFAULT_CAPTURE_BUTTON_STATE
46 import com.google.jetpackcamera.feature.preview.FlashModeUiState
47 import com.google.jetpackcamera.feature.preview.PreviewMode
48 import com.google.jetpackcamera.feature.preview.PreviewUiState
49 import com.google.jetpackcamera.feature.preview.R
50 import com.google.jetpackcamera.feature.preview.quicksettings.ui.FocusedQuickSetRatio
51 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON
52 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLASH_BUTTON
53 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLIP_CAMERA_BUTTON
54 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_HDR_BUTTON
55 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_BUTTON
56 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_STREAM_CONFIG_BUTTON
57 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickFlipCamera
58 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetConcurrentCamera
59 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetFlash
60 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetHdr
61 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetRatio
62 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetStreamConfig
63 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsGrid
64 import com.google.jetpackcamera.settings.model.AspectRatio
65 import com.google.jetpackcamera.settings.model.CameraAppSettings
66 import com.google.jetpackcamera.settings.model.CameraConstraints
67 import com.google.jetpackcamera.settings.model.ConcurrentCameraMode
68 import com.google.jetpackcamera.settings.model.DEFAULT_HDR_DYNAMIC_RANGE
69 import com.google.jetpackcamera.settings.model.DEFAULT_HDR_IMAGE_OUTPUT
70 import com.google.jetpackcamera.settings.model.DynamicRange
71 import com.google.jetpackcamera.settings.model.FlashMode
72 import com.google.jetpackcamera.settings.model.ImageOutputFormat
73 import com.google.jetpackcamera.settings.model.LensFacing
74 import com.google.jetpackcamera.settings.model.StreamConfig
75 import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
76 import com.google.jetpackcamera.settings.model.forCurrentLens
77 
78 /**
79  * The UI component for quick settings.
80  */
81 @Composable
82 fun QuickSettingsScreenOverlay(
83     previewUiState: PreviewUiState.Ready,
84     currentCameraSettings: CameraAppSettings,
85     toggleIsOpen: () -> Unit,
86     onLensFaceClick: (lensFace: LensFacing) -> Unit,
87     onFlashModeClick: (flashMode: FlashMode) -> Unit,
88     onAspectRatioClick: (aspectRation: AspectRatio) -> Unit,
89     onStreamConfigClick: (streamConfig: StreamConfig) -> Unit,
90     onDynamicRangeClick: (dynamicRange: DynamicRange) -> Unit,
91     onImageOutputFormatClick: (imageOutputFormat: ImageOutputFormat) -> Unit,
92     onConcurrentCameraModeClick: (concurrentCameraMode: ConcurrentCameraMode) -> Unit,
93     modifier: Modifier = Modifier,
94     isOpen: Boolean = false
95 ) {
96     var focusedQuickSetting by remember {
97         mutableStateOf(FocusedQuickSetting.NONE)
98     }
99 
100     AnimatedVisibility(
101         visible = isOpen,
102         enter = slideInVertically(initialOffsetY = { -it / 8 }) + fadeIn(),
103         exit = slideOutVertically(targetOffsetY = { -it / 16 }) + fadeOut()
104     ) {
105         val onBack = {
106             when (focusedQuickSetting) {
107                 FocusedQuickSetting.NONE -> toggleIsOpen()
108                 else -> focusedQuickSetting = FocusedQuickSetting.NONE
109             }
110         }
111         // close out of focused quick setting
112         if (!isOpen) {
113             focusedQuickSetting = FocusedQuickSetting.NONE
114         }
115 
116         BackHandler(onBack = onBack)
117         Column(
118             modifier =
119             modifier
120                 .fillMaxSize()
121                 .background(color = Color.Black.copy(alpha = 0.7f))
122                 .clickable(
123                     onClick = onBack,
124                     indication = null,
125                     interactionSource = remember {
126                         MutableInteractionSource()
127                     }
128                 ),
129             verticalArrangement = Arrangement.Center,
130             horizontalAlignment = Alignment.CenterHorizontally
131         ) {
132             ExpandedQuickSettingsUi(
133                 previewUiState = previewUiState,
134                 currentCameraSettings = currentCameraSettings,
135                 focusedQuickSetting = focusedQuickSetting,
136                 setFocusedQuickSetting = { enum: FocusedQuickSetting ->
137                     focusedQuickSetting = enum
138                 },
139                 onLensFaceClick = onLensFaceClick,
140                 onFlashModeClick = onFlashModeClick,
141                 onAspectRatioClick = onAspectRatioClick,
142                 onStreamConfigClick = onStreamConfigClick,
143                 onDynamicRangeClick = onDynamicRangeClick,
144                 onImageOutputFormatClick = onImageOutputFormatClick,
145                 onConcurrentCameraModeClick = onConcurrentCameraModeClick
146             )
147         }
148     }
149 }
150 
151 // enum representing which individual quick setting is currently focused
152 private enum class FocusedQuickSetting {
153     NONE,
154     ASPECT_RATIO
155 }
156 
157 // todo: Add UI states for Quick Settings buttons
158 
159 /**
160  * The UI component for quick settings when it is focused.
161  */
162 @Composable
ExpandedQuickSettingsUinull163 private fun ExpandedQuickSettingsUi(
164     modifier: Modifier = Modifier,
165     previewUiState: PreviewUiState.Ready,
166     currentCameraSettings: CameraAppSettings,
167     onLensFaceClick: (newLensFace: LensFacing) -> Unit,
168     onFlashModeClick: (flashMode: FlashMode) -> Unit,
169     onAspectRatioClick: (aspectRation: AspectRatio) -> Unit,
170     onStreamConfigClick: (streamConfig: StreamConfig) -> Unit,
171     focusedQuickSetting: FocusedQuickSetting,
172     setFocusedQuickSetting: (FocusedQuickSetting) -> Unit,
173     onDynamicRangeClick: (dynamicRange: DynamicRange) -> Unit,
174     onImageOutputFormatClick: (imageOutputFormat: ImageOutputFormat) -> Unit,
175     onConcurrentCameraModeClick: (concurrentCameraMode: ConcurrentCameraMode) -> Unit
176 ) {
177     Column(
178         modifier =
179         modifier
180             .padding(
181                 horizontal = dimensionResource(
182                     id = R.dimen.quick_settings_ui_horizontal_padding
183                 )
184             )
185     ) {
186         // if no setting is chosen, display the grid of settings
187         // to change the order of display just move these lines of code above or below each other
188         AnimatedVisibility(visible = focusedQuickSetting == FocusedQuickSetting.NONE) {
189             val displayedQuickSettings: List<@Composable () -> Unit> =
190                 buildList {
191                     add {
192                         QuickSetFlash(
193                             modifier = Modifier.testTag(QUICK_SETTINGS_FLASH_BUTTON),
194                             onClick = { f: FlashMode -> onFlashModeClick(f) },
195                             flashModeUiState = previewUiState.flashModeUiState
196                         )
197                     }
198 
199                     add {
200                         QuickFlipCamera(
201                             modifier = Modifier.testTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON),
202                             setLensFacing = { l: LensFacing -> onLensFaceClick(l) },
203                             currentLensFacing = currentCameraSettings.cameraLensFacing
204                         )
205                     }
206 
207                     add {
208                         QuickSetRatio(
209                             modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_BUTTON),
210                             onClick = {
211                                 setFocusedQuickSetting(
212                                     FocusedQuickSetting.ASPECT_RATIO
213                                 )
214                             },
215                             ratio = currentCameraSettings.aspectRatio,
216                             currentRatio = currentCameraSettings.aspectRatio
217                         )
218                     }
219 
220                     add {
221                         QuickSetStreamConfig(
222                             modifier = Modifier.testTag(
223                                 QUICK_SETTINGS_STREAM_CONFIG_BUTTON
224                             ),
225                             setStreamConfig = { c: StreamConfig -> onStreamConfigClick(c) },
226                             currentStreamConfig = currentCameraSettings.streamConfig,
227                             enabled = !(
228                                 currentCameraSettings.concurrentCameraMode ==
229                                     ConcurrentCameraMode.DUAL ||
230                                     currentCameraSettings.imageFormat ==
231                                     ImageOutputFormat.JPEG_ULTRA_HDR
232                                 )
233                         )
234                     }
235 
236                     val cameraConstraints = previewUiState.systemConstraints.forCurrentLens(
237                         currentCameraSettings
238                     )
239                     add {
240                         fun CameraConstraints.hdrDynamicRangeSupported(): Boolean =
241                             this.supportedDynamicRanges.size > 1
242 
243                         fun CameraConstraints.hdrImageFormatSupported(): Boolean =
244                             supportedImageFormatsMap[currentCameraSettings.streamConfig]
245                                 ?.let { it.size > 1 } == true
246 
247                         // TODO(tm): Move this to PreviewUiState
248                         fun shouldEnable(): Boolean = when {
249                             currentCameraSettings.concurrentCameraMode !=
250                                 ConcurrentCameraMode.OFF -> false
251                             else -> (
252                                 cameraConstraints?.hdrDynamicRangeSupported() == true &&
253                                     previewUiState.previewMode is PreviewMode.StandardMode
254                                 ) ||
255                                 cameraConstraints?.hdrImageFormatSupported() == true
256                         }
257 
258                         QuickSetHdr(
259                             modifier = Modifier.testTag(QUICK_SETTINGS_HDR_BUTTON),
260                             onClick = { d: DynamicRange, i: ImageOutputFormat ->
261                                 onDynamicRangeClick(d)
262                                 onImageOutputFormatClick(i)
263                             },
264                             selectedDynamicRange = currentCameraSettings.dynamicRange,
265                             selectedImageOutputFormat = currentCameraSettings.imageFormat,
266                             hdrDynamicRangeSupported =
267                             cameraConstraints?.hdrDynamicRangeSupported() == true,
268                             previewMode = previewUiState.previewMode,
269                             enabled = shouldEnable()
270                         )
271                     }
272 
273                     add {
274                         QuickSetConcurrentCamera(
275                             modifier =
276                             Modifier.testTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON),
277                             setConcurrentCameraMode = { c: ConcurrentCameraMode ->
278                                 onConcurrentCameraModeClick(c)
279                             },
280                             currentConcurrentCameraMode =
281                             currentCameraSettings.concurrentCameraMode,
282                             enabled =
283                             previewUiState.systemConstraints.concurrentCamerasSupported &&
284                                 previewUiState.previewMode
285                                     !is PreviewMode.ExternalImageCaptureMode &&
286                                 (
287                                     currentCameraSettings.dynamicRange !=
288                                         DEFAULT_HDR_DYNAMIC_RANGE &&
289                                         currentCameraSettings.imageFormat !=
290                                         DEFAULT_HDR_IMAGE_OUTPUT
291                                     )
292                         )
293                     }
294                 }
295             QuickSettingsGrid(quickSettingsButtons = displayedQuickSettings)
296         }
297         // if a setting that can be focused is selected, show it
298         AnimatedVisibility(visible = focusedQuickSetting == FocusedQuickSetting.ASPECT_RATIO) {
299             FocusedQuickSetRatio(
300                 setRatio = onAspectRatioClick,
301                 currentRatio = currentCameraSettings.aspectRatio
302             )
303         }
304     }
305 }
306 
307 @Preview
308 @Composable
ExpandedQuickSettingsUiPreviewnull309 fun ExpandedQuickSettingsUiPreview() {
310     MaterialTheme {
311         ExpandedQuickSettingsUi(
312             previewUiState = PreviewUiState.Ready(
313                 currentCameraSettings = CameraAppSettings(),
314                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
315                 previewMode = PreviewMode.StandardMode {},
316                 videoRecordingState = VideoRecordingState.Inactive(),
317                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
318                 flashModeUiState = FlashModeUiState.Available(
319                     selectedFlashMode = FlashMode.OFF,
320                     availableFlashModes = listOf(FlashMode.OFF, FlashMode.ON),
321                     isActive = false
322                 ),
323                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
324             ),
325             currentCameraSettings = CameraAppSettings(),
326             onLensFaceClick = { },
327             onFlashModeClick = { },
328             focusedQuickSetting = FocusedQuickSetting.NONE,
329             setFocusedQuickSetting = { },
330             onAspectRatioClick = { },
331             onStreamConfigClick = { },
332             onDynamicRangeClick = { },
333             onImageOutputFormatClick = { },
334             onConcurrentCameraModeClick = { }
335         )
336     }
337 }
338 
339 @Preview
340 @Composable
ExpandedQuickSettingsUiPreview_WithHdrnull341 fun ExpandedQuickSettingsUiPreview_WithHdr() {
342     MaterialTheme {
343         ExpandedQuickSettingsUi(
344             previewUiState = PreviewUiState.Ready(
345                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
346                 previewMode = PreviewMode.StandardMode {},
347                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
348                 videoRecordingState = VideoRecordingState.Inactive(),
349                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
350             ),
351             currentCameraSettings = CameraAppSettings(dynamicRange = DynamicRange.HLG10),
352             onLensFaceClick = { },
353             onFlashModeClick = { },
354             focusedQuickSetting = FocusedQuickSetting.NONE,
355             setFocusedQuickSetting = { },
356             onAspectRatioClick = { },
357             onStreamConfigClick = { },
358             onDynamicRangeClick = { },
359             onImageOutputFormatClick = { },
360             onConcurrentCameraModeClick = { }
361         )
362     }
363 }
364 
365 private val TYPICAL_SYSTEM_CONSTRAINTS_WITH_HDR =
366     TYPICAL_SYSTEM_CONSTRAINTS.copy(
367         perLensConstraints = TYPICAL_SYSTEM_CONSTRAINTS
lensFacingnull368             .perLensConstraints.entries.associate { (lensFacing, constraints) ->
369                 lensFacing to constraints.copy(
370                     supportedDynamicRanges = setOf(DynamicRange.SDR, DynamicRange.HLG10)
371                 )
372             }
373     )
374