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.animateColorAsState
20 import androidx.compose.animation.core.animateFloatAsState
21 import androidx.compose.animation.core.tween
22 import androidx.compose.foundation.background
23 import androidx.compose.foundation.clickable
24 import androidx.compose.foundation.layout.Arrangement
25 import androidx.compose.foundation.layout.Column
26 import androidx.compose.foundation.layout.fillMaxSize
27 import androidx.compose.foundation.layout.padding
28 import androidx.compose.material3.MaterialTheme
29 import androidx.compose.runtime.Composable
30 import androidx.compose.runtime.getValue
31 import androidx.compose.runtime.mutableStateOf
32 import androidx.compose.runtime.remember
33 import androidx.compose.runtime.setValue
34 import androidx.compose.ui.Alignment
35 import androidx.compose.ui.Modifier
36 import androidx.compose.ui.draw.alpha
37 import androidx.compose.ui.graphics.Color
38 import androidx.compose.ui.platform.testTag
39 import androidx.compose.ui.res.dimensionResource
40 import androidx.compose.ui.tooling.preview.Preview
41 import com.google.jetpackcamera.feature.preview.CaptureModeToggleUiState
42 import com.google.jetpackcamera.feature.preview.PreviewMode
43 import com.google.jetpackcamera.feature.preview.PreviewUiState
44 import com.google.jetpackcamera.feature.preview.R
45 import com.google.jetpackcamera.feature.preview.quicksettings.ui.ExpandedQuickSetRatio
46 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_CAPTURE_MODE_BUTTON
47 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON
48 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLASH_BUTTON
49 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLIP_CAMERA_BUTTON
50 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_HDR_BUTTON
51 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_BUTTON
52 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickFlipCamera
53 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetCaptureMode
54 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetConcurrentCamera
55 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetFlash
56 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetHdr
57 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSetRatio
58 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsGrid
59 import com.google.jetpackcamera.settings.model.AspectRatio
60 import com.google.jetpackcamera.settings.model.CameraAppSettings
61 import com.google.jetpackcamera.settings.model.CameraConstraints
62 import com.google.jetpackcamera.settings.model.CaptureMode
63 import com.google.jetpackcamera.settings.model.ConcurrentCameraMode
64 import com.google.jetpackcamera.settings.model.DynamicRange
65 import com.google.jetpackcamera.settings.model.FlashMode
66 import com.google.jetpackcamera.settings.model.ImageOutputFormat
67 import com.google.jetpackcamera.settings.model.LensFacing
68 import com.google.jetpackcamera.settings.model.LowLightBoost
69 import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
70 import com.google.jetpackcamera.settings.model.forCurrentLens
71 
72 /**
73  * The UI component for quick settings.
74  */
75 @Composable
76 fun QuickSettingsScreenOverlay(
77     previewUiState: PreviewUiState.Ready,
78     currentCameraSettings: CameraAppSettings,
79     toggleIsOpen: () -> Unit,
80     onLensFaceClick: (lensFace: LensFacing) -> Unit,
81     onFlashModeClick: (flashMode: FlashMode) -> Unit,
82     onAspectRatioClick: (aspectRation: AspectRatio) -> Unit,
83     onCaptureModeClick: (captureMode: CaptureMode) -> Unit,
84     onDynamicRangeClick: (dynamicRange: DynamicRange) -> Unit,
85     onImageOutputFormatClick: (imageOutputFormat: ImageOutputFormat) -> Unit,
86     onConcurrentCameraModeClick: (concurrentCameraMode: ConcurrentCameraMode) -> Unit,
87     onLowLightBoostClick: (lowLightBoost: LowLightBoost) -> Unit,
88     modifier: Modifier = Modifier,
89     isOpen: Boolean = false
90 ) {
91     var shouldShowQuickSetting by remember {
92         mutableStateOf(IsExpandedQuickSetting.NONE)
93     }
94 
95     val backgroundColor =
96         animateColorAsState(
97             targetValue = Color.Black.copy(alpha = if (isOpen) 0.7f else 0f),
98             label = "backgroundColorAnimation"
99         )
100 
101     val contentAlpha =
102         animateFloatAsState(
103             targetValue = if (isOpen) 1f else 0f,
104             label = "contentAlphaAnimation",
105             animationSpec = tween()
106         )
107 
108     if (isOpen) {
109         val onBack = {
110             when (shouldShowQuickSetting) {
111                 IsExpandedQuickSetting.NONE -> toggleIsOpen()
112                 else -> shouldShowQuickSetting = IsExpandedQuickSetting.NONE
113             }
114         }
115         BackHandler(onBack = onBack)
116         Column(
117             modifier =
118             modifier
119                 .fillMaxSize()
120                 .background(color = backgroundColor.value)
121                 .alpha(alpha = contentAlpha.value)
122                 .clickable(onClick = onBack),
123             verticalArrangement = Arrangement.Center,
124             horizontalAlignment = Alignment.CenterHorizontally
125         ) {
126             ExpandedQuickSettingsUi(
127                 previewUiState = previewUiState,
128                 currentCameraSettings = currentCameraSettings,
129                 shouldShowQuickSetting = shouldShowQuickSetting,
130                 setVisibleQuickSetting = { enum: IsExpandedQuickSetting ->
131                     shouldShowQuickSetting = enum
132                 },
133                 onLensFaceClick = onLensFaceClick,
134                 onFlashModeClick = onFlashModeClick,
135                 onAspectRatioClick = onAspectRatioClick,
136                 onCaptureModeClick = onCaptureModeClick,
137                 onDynamicRangeClick = onDynamicRangeClick,
138                 onImageOutputFormatClick = onImageOutputFormatClick,
139                 onConcurrentCameraModeClick = onConcurrentCameraModeClick,
140                 onLowLightBoostClick = onLowLightBoostClick
141             )
142         }
143     } else {
144         shouldShowQuickSetting = IsExpandedQuickSetting.NONE
145     }
146 }
147 
148 // enum representing which individual quick setting is currently expanded
149 private enum class IsExpandedQuickSetting {
150     NONE,
151     ASPECT_RATIO
152 }
153 
154 /**
155  * The UI component for quick settings when it is expanded.
156  */
157 @Composable
ExpandedQuickSettingsUinull158 private fun ExpandedQuickSettingsUi(
159     previewUiState: PreviewUiState.Ready,
160     currentCameraSettings: CameraAppSettings,
161     onLensFaceClick: (newLensFace: LensFacing) -> Unit,
162     onFlashModeClick: (flashMode: FlashMode) -> Unit,
163     onAspectRatioClick: (aspectRation: AspectRatio) -> Unit,
164     onCaptureModeClick: (captureMode: CaptureMode) -> Unit,
165     shouldShowQuickSetting: IsExpandedQuickSetting,
166     setVisibleQuickSetting: (IsExpandedQuickSetting) -> Unit,
167     onDynamicRangeClick: (dynamicRange: DynamicRange) -> Unit,
168     onImageOutputFormatClick: (imageOutputFormat: ImageOutputFormat) -> Unit,
169     onConcurrentCameraModeClick: (concurrentCameraMode: ConcurrentCameraMode) -> Unit,
170     onLowLightBoostClick: (lowLightBoost: LowLightBoost) -> Unit
171 ) {
172     Column(
173         modifier =
174         Modifier
175             .padding(
176                 horizontal = dimensionResource(
177                     id = R.dimen.quick_settings_ui_horizontal_padding
178                 )
179             )
180     ) {
181         // if no setting is chosen, display the grid of settings
182         // to change the order of display just move these lines of code above or below each other
183         when (shouldShowQuickSetting) {
184             IsExpandedQuickSetting.NONE -> {
185                 val displayedQuickSettings: List<@Composable () -> Unit> =
186                     buildList {
187                         add {
188                             QuickSetFlash(
189                                 modifier = Modifier.testTag(QUICK_SETTINGS_FLASH_BUTTON),
190                                 onClick = { f: FlashMode -> onFlashModeClick(f) },
191                                 currentFlashMode = currentCameraSettings.flashMode
192                             )
193                         }
194 
195                         add {
196                             QuickFlipCamera(
197                                 modifier = Modifier.testTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON),
198                                 setLensFacing = { l: LensFacing -> onLensFaceClick(l) },
199                                 currentLensFacing = currentCameraSettings.cameraLensFacing
200                             )
201                         }
202 
203                         add {
204                             QuickSetRatio(
205                                 modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_BUTTON),
206                                 onClick = {
207                                     setVisibleQuickSetting(
208                                         IsExpandedQuickSetting.ASPECT_RATIO
209                                     )
210                                 },
211                                 ratio = currentCameraSettings.aspectRatio,
212                                 currentRatio = currentCameraSettings.aspectRatio
213                             )
214                         }
215 
216                         add {
217                             QuickSetCaptureMode(
218                                 modifier = Modifier.testTag(QUICK_SETTINGS_CAPTURE_MODE_BUTTON),
219                                 setCaptureMode = { c: CaptureMode -> onCaptureModeClick(c) },
220                                 currentCaptureMode = currentCameraSettings.captureMode,
221                                 enabled = currentCameraSettings.concurrentCameraMode ==
222                                     ConcurrentCameraMode.OFF
223                             )
224                         }
225 
226                         val cameraConstraints = previewUiState.systemConstraints.forCurrentLens(
227                             currentCameraSettings
228                         )
229                         add {
230                             fun CameraConstraints.hdrDynamicRangeSupported(): Boolean =
231                                 this.supportedDynamicRanges.size > 1
232 
233                             fun CameraConstraints.hdrImageFormatSupported(): Boolean =
234                                 supportedImageFormatsMap[currentCameraSettings.captureMode]
235                                     ?.let { it.size > 1 } ?: false
236 
237                             // TODO(tm): Move this to PreviewUiState
238                             fun shouldEnable(): Boolean = when {
239                                 currentCameraSettings.concurrentCameraMode !=
240                                     ConcurrentCameraMode.OFF -> false
241                                 else -> (
242                                     cameraConstraints?.hdrDynamicRangeSupported() == true &&
243                                         previewUiState.previewMode is PreviewMode.StandardMode
244                                     ) ||
245                                     cameraConstraints?.hdrImageFormatSupported() == true
246                             }
247 
248                             QuickSetHdr(
249                                 modifier = Modifier.testTag(QUICK_SETTINGS_HDR_BUTTON),
250                                 onClick = { d: DynamicRange, i: ImageOutputFormat ->
251                                     onDynamicRangeClick(d)
252                                     onImageOutputFormatClick(i)
253                                 },
254                                 selectedDynamicRange = currentCameraSettings.dynamicRange,
255                                 selectedImageOutputFormat = currentCameraSettings.imageFormat,
256                                 hdrDynamicRange = currentCameraSettings.defaultHdrDynamicRange,
257                                 hdrImageFormat = currentCameraSettings.defaultHdrImageOutputFormat,
258                                 hdrDynamicRangeSupported =
259                                 cameraConstraints?.hdrDynamicRangeSupported() ?: false,
260                                 previewMode = previewUiState.previewMode,
261                                 enabled = shouldEnable()
262                             )
263                         }
264 
265                         add {
266                             QuickSetConcurrentCamera(
267                                 modifier =
268                                 Modifier.testTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON),
269                                 setConcurrentCameraMode = { c: ConcurrentCameraMode ->
270                                     onConcurrentCameraModeClick(c)
271                                 },
272                                 currentConcurrentCameraMode =
273                                 currentCameraSettings.concurrentCameraMode,
274                                 enabled =
275                                 previewUiState.systemConstraints.concurrentCamerasSupported &&
276                                     previewUiState.previewMode
277                                         !is PreviewMode.ExternalImageCaptureMode
278                             )
279                         }
280                     }
281                 QuickSettingsGrid(quickSettingsButtons = displayedQuickSettings)
282             }
283             // if a setting that can be expanded is selected, show it
284             IsExpandedQuickSetting.ASPECT_RATIO -> {
285                 ExpandedQuickSetRatio(
286                     setRatio = onAspectRatioClick,
287                     currentRatio = currentCameraSettings.aspectRatio
288                 )
289             }
290         }
291     }
292 }
293 
294 @Preview
295 @Composable
ExpandedQuickSettingsUiPreviewnull296 fun ExpandedQuickSettingsUiPreview() {
297     MaterialTheme {
298         ExpandedQuickSettingsUi(
299             previewUiState = PreviewUiState.Ready(
300                 currentCameraSettings = CameraAppSettings(),
301                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
302                 previewMode = PreviewMode.StandardMode {},
303                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible
304             ),
305             currentCameraSettings = CameraAppSettings(),
306             onLensFaceClick = { },
307             onFlashModeClick = { },
308             shouldShowQuickSetting = IsExpandedQuickSetting.NONE,
309             setVisibleQuickSetting = { },
310             onAspectRatioClick = { },
311             onCaptureModeClick = { },
312             onDynamicRangeClick = { },
313             onImageOutputFormatClick = { },
314             onConcurrentCameraModeClick = { },
315             onLowLightBoostClick = { }
316         )
317     }
318 }
319 
320 @Preview
321 @Composable
ExpandedQuickSettingsUiPreview_WithHdrnull322 fun ExpandedQuickSettingsUiPreview_WithHdr() {
323     MaterialTheme {
324         ExpandedQuickSettingsUi(
325             previewUiState = PreviewUiState.Ready(
326                 currentCameraSettings = CameraAppSettings(),
327                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
328                 previewMode = PreviewMode.StandardMode {},
329                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible
330             ),
331             currentCameraSettings = CameraAppSettings(dynamicRange = DynamicRange.HLG10),
332             onLensFaceClick = { },
333             onFlashModeClick = { },
334             shouldShowQuickSetting = IsExpandedQuickSetting.NONE,
335             setVisibleQuickSetting = { },
336             onAspectRatioClick = { },
337             onCaptureModeClick = { },
338             onDynamicRangeClick = { },
339             onImageOutputFormatClick = { },
340             onConcurrentCameraModeClick = { },
341             onLowLightBoostClick = { }
342         )
343     }
344 }
345 
346 private val TYPICAL_SYSTEM_CONSTRAINTS_WITH_HDR =
347     TYPICAL_SYSTEM_CONSTRAINTS.copy(
348         perLensConstraints = TYPICAL_SYSTEM_CONSTRAINTS.perLensConstraints.entries.associate {
lensFacingnull349                 (lensFacing, constraints) ->
350             lensFacing to constraints.copy(
351                 supportedDynamicRanges = setOf(DynamicRange.SDR, DynamicRange.HLG10)
352             )
353         }
354     )
355