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