• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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