1 /*
2 * Copyright (C) 2024 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.ui.debug
17
18 import android.util.Log
19 import androidx.activity.compose.BackHandler
20 import androidx.compose.animation.animateColorAsState
21 import androidx.compose.animation.core.animateFloatAsState
22 import androidx.compose.animation.core.tween
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.clickable
25 import androidx.compose.foundation.layout.Arrangement
26 import androidx.compose.foundation.layout.Box
27 import androidx.compose.foundation.layout.Column
28 import androidx.compose.foundation.layout.Row
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.foundation.rememberScrollState
31 import androidx.compose.foundation.text.KeyboardOptions
32 import androidx.compose.foundation.verticalScroll
33 import androidx.compose.material3.Text
34 import androidx.compose.material3.TextButton
35 import androidx.compose.material3.TextField
36 import androidx.compose.runtime.Composable
37 import androidx.compose.runtime.getValue
38 import androidx.compose.runtime.mutableStateOf
39 import androidx.compose.runtime.remember
40 import androidx.compose.runtime.setValue
41 import androidx.compose.ui.Alignment
42 import androidx.compose.ui.Modifier
43 import androidx.compose.ui.draw.alpha
44 import androidx.compose.ui.graphics.Color
45 import androidx.compose.ui.platform.testTag
46 import androidx.compose.ui.text.input.KeyboardType
47 import androidx.compose.ui.unit.sp
48 import com.google.jetpackcamera.feature.preview.PreviewUiState
49 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_BUTTON
50 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_CAMERA_PROPERTIES_TAG
51 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON
52 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON
53 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD
54 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_SHOW_CAMERA_PROPERTIES_BUTTON
55 import com.google.jetpackcamera.feature.preview.ui.DEBUG_OVERLAY_VIDEO_RESOLUTION_TAG
56 import kotlin.math.abs
57
58 private const val TAG = "DebugOverlayComponents"
59
60 @Composable
DebugOverlayToggleButtonnull61 fun DebugOverlayToggleButton(modifier: Modifier = Modifier, toggleIsOpen: () -> Unit) {
62 TextButton(modifier = modifier.testTag(DEBUG_OVERLAY_BUTTON), onClick = { toggleIsOpen() }) {
63 Text(text = "Debug")
64 }
65 }
66
67 @Composable
DebugOverlayComponentnull68 fun DebugOverlayComponent(
69 modifier: Modifier = Modifier,
70 onChangeZoomScale: (Float) -> Unit,
71 toggleIsOpen: () -> Unit,
72 previewUiState: PreviewUiState.Ready
73 ) {
74 val isOpen = previewUiState.debugUiState.isDebugMode &&
75 previewUiState.debugUiState.isDebugOverlayOpen
76 val backgroundColor =
77 animateColorAsState(
78 targetValue = Color.Black.copy(alpha = if (isOpen) 0.7f else 0f),
79 label = "backgroundColorAnimation"
80 )
81
82 val contentAlpha =
83 animateFloatAsState(
84 targetValue = if (isOpen) 1f else 0f,
85 label = "contentAlphaAnimation",
86 animationSpec = tween()
87 )
88
89 val zoomRatioDialog = remember { mutableStateOf(false) }
90 val cameraPropertiesJSONDialog = remember { mutableStateOf(false) }
91
92 if (isOpen) {
93 BackHandler(onBack = { toggleIsOpen() })
94
95 Box(
96 modifier = modifier
97 .fillMaxSize()
98 .background(color = backgroundColor.value)
99 .alpha(alpha = contentAlpha.value)
100 .clickable(onClick = { toggleIsOpen() })
101 ) {
102 // Buttons
103 Column(
104 modifier = Modifier.fillMaxSize(),
105 verticalArrangement = Arrangement.Center,
106 horizontalAlignment = Alignment.CenterHorizontally
107 ) {
108 TextButton(
109 modifier = Modifier.testTag(
110 DEBUG_OVERLAY_SHOW_CAMERA_PROPERTIES_BUTTON
111 ),
112 onClick = {
113 cameraPropertiesJSONDialog.value = true
114 }
115 ) {
116 Text(text = "Show Camera Properties JSON")
117 }
118
119 Row {
120 Text("Video resolution: ")
121 val videoResText = if (previewUiState.debugUiState.videoResolution == null) {
122 "null"
123 } else {
124 val size = previewUiState.debugUiState.videoResolution
125 abs(size.height).toString() + "x" + abs(size.width).toString()
126 }
127 Text(
128 modifier = Modifier.testTag(
129 DEBUG_OVERLAY_VIDEO_RESOLUTION_TAG
130 ),
131 text = videoResText
132 )
133 }
134
135 TextButton(
136 modifier = Modifier.testTag(
137 DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON
138 ),
139 onClick = {
140 zoomRatioDialog.value = true
141 }
142 ) {
143 Text(text = "Set Zoom Ratio")
144 }
145 }
146
147 // Openable contents
148 // Show Camera properties
149 if (cameraPropertiesJSONDialog.value) {
150 CameraPropertiesJSONComponent(previewUiState) {
151 cameraPropertiesJSONDialog.value = false
152 }
153 }
154
155 // Set zoom ratio
156 if (zoomRatioDialog.value) {
157 SetZoomRatioComponent(previewUiState, onChangeZoomScale) {
158 zoomRatioDialog.value = false
159 }
160 }
161 }
162 }
163 }
164
165 @Composable
CameraPropertiesJSONComponentnull166 private fun CameraPropertiesJSONComponent(
167 previewUiState: PreviewUiState.Ready,
168 onClose: () -> Unit
169 ) {
170 BackHandler(onBack = { onClose() })
171 val scrollState = rememberScrollState()
172 Column(
173 modifier = Modifier
174 .fillMaxSize()
175 .verticalScroll(state = scrollState)
176 .background(color = Color.Black)
177 ) {
178 Text(
179 modifier = Modifier.testTag(DEBUG_OVERLAY_CAMERA_PROPERTIES_TAG),
180 text = previewUiState.debugUiState.cameraPropertiesJSON,
181 fontSize = 10.sp
182 )
183 }
184 }
185
186 @Composable
SetZoomRatioComponentnull187 private fun SetZoomRatioComponent(
188 previewUiState: PreviewUiState.Ready,
189 onChangeZoomScale: (Float) -> Unit,
190 onClose: () -> Unit
191 ) {
192 var zoomRatioText = remember { mutableStateOf("") }
193 BackHandler(onBack = { onClose() })
194 val scrollState = rememberScrollState()
195 Column(
196 modifier = Modifier
197 .fillMaxSize()
198 .verticalScroll(state = scrollState)
199 .background(color = Color.Black)
200 ) {
201 Text(text = "Enter and confirm zoom ratio (Absolute not relative)")
202 TextField(
203 modifier = Modifier.testTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD),
204 value = zoomRatioText.value,
205 onValueChange = { zoomRatioText.value = it },
206 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
207 )
208 TextButton(
209 modifier = Modifier.testTag(
210 DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON
211 ),
212 onClick = {
213 try {
214 val relativeRatio = if (zoomRatioText.value.isEmpty()) {
215 1f
216 } else {
217 zoomRatioText.value.toFloat()
218 }
219 val currentRatio = previewUiState.zoomScale
220 val absoluteRatio = relativeRatio / currentRatio
221 onChangeZoomScale(absoluteRatio)
222 } catch (e: NumberFormatException) {
223 Log.d(TAG, "Zoom ratio should be a float")
224 }
225 onClose()
226 }
227 ) {
228 Text(text = "Set")
229 }
230 }
231 }
232