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