1 /*
2 * Copyright 2022 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
17 package androidx.camera.integration.avsync
18
19 import androidx.camera.integration.avsync.model.CameraHelper.Companion.CameraImplementation
20 import androidx.camera.integration.avsync.ui.theme.LightOff
21 import androidx.camera.integration.avsync.ui.theme.LightOn
22 import androidx.camera.integration.avsync.ui.widget.AdvancedFloatingActionButton
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.layout.Box
25 import androidx.compose.foundation.layout.Column
26 import androidx.compose.foundation.layout.Row
27 import androidx.compose.foundation.layout.Spacer
28 import androidx.compose.foundation.layout.fillMaxHeight
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.foundation.layout.size
31 import androidx.compose.material.Icon
32 import androidx.compose.material.icons.Icons
33 import androidx.compose.material.icons.filled.Close
34 import androidx.compose.material.icons.filled.PlayArrow
35 import androidx.compose.runtime.Composable
36 import androidx.compose.runtime.LaunchedEffect
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.LocalContext
41 import androidx.compose.ui.res.painterResource
42 import androidx.compose.ui.res.stringResource
43 import androidx.compose.ui.tooling.preview.Preview
44 import androidx.compose.ui.unit.dp
45 import androidx.lifecycle.compose.LocalLifecycleOwner
46 import androidx.lifecycle.viewmodel.compose.viewModel
47
48 @Composable
SignalGeneratorScreennull49 fun SignalGeneratorScreen(
50 beepFrequency: Int,
51 beepEnabled: Boolean,
52 cameraImplementation: CameraImplementation,
53 viewModel: SignalGeneratorViewModel = viewModel()
54 ) {
55 val context = LocalContext.current
56 val lifecycleOwner = LocalLifecycleOwner.current
57
58 LaunchedEffect(true) {
59 viewModel.initialRecorder(context, lifecycleOwner, cameraImplementation)
60 viewModel.initialSignalGenerator(context, beepFrequency, beepEnabled)
61 }
62
63 MainContent(
64 isGeneratorReady = viewModel.isGeneratorReady,
65 isRecorderReady = viewModel.isRecorderReady,
66 isSignalActive = viewModel.isActivePeriod,
67 isSignalStarted = viewModel.isSignalGenerating,
68 isRecording = viewModel.isRecording,
69 isPaused = viewModel.isPaused,
70 onSignalStartClick = { viewModel.startSignalGeneration() },
71 onSignalStopClick = { viewModel.stopSignalGeneration() },
72 onRecordingStartClick = { viewModel.startRecording(context) },
73 onRecordingStopClick = { viewModel.stopRecording() },
74 onRecordingPauseClick = { viewModel.pauseRecording() },
75 onRecordingResumeClick = { viewModel.resumeRecording() },
76 )
77 }
78
79 @Composable
MainContentnull80 private fun MainContent(
81 isGeneratorReady: Boolean = false,
82 isRecorderReady: Boolean = false,
83 isSignalActive: Boolean = false,
84 isSignalStarted: Boolean = false,
85 isRecording: Boolean = false,
86 isPaused: Boolean = false,
87 onSignalStartClick: () -> Unit = {},
<lambda>null88 onSignalStopClick: () -> Unit = {},
<lambda>null89 onRecordingStartClick: () -> Unit = {},
<lambda>null90 onRecordingStopClick: () -> Unit = {},
<lambda>null91 onRecordingPauseClick: () -> Unit = {},
<lambda>null92 onRecordingResumeClick: () -> Unit = {},
93 ) {
<lambda>null94 Box(modifier = Modifier.fillMaxSize()) {
95 LightingScreen(isOn = isSignalActive)
96 ControlPanel {
97 SignalControl(
98 enabled = isGeneratorReady,
99 isStarted = isSignalStarted,
100 onStartClick = onSignalStartClick,
101 onStopClick = onSignalStopClick,
102 )
103 RecordingControl(
104 enabled = isRecorderReady,
105 isStarted = isRecording,
106 isPaused = isPaused,
107 onStartClick = onRecordingStartClick,
108 onStopClick = onRecordingStopClick,
109 onPauseClick = onRecordingPauseClick,
110 onResumeClick = onRecordingResumeClick,
111 )
112 }
113 }
114 }
115
116 @Composable
LightingScreennull117 private fun LightingScreen(modifier: Modifier = Modifier, isOn: Boolean = false) {
118 val backgroundColor = if (isOn) LightOn else LightOff
119 Box(modifier = modifier.fillMaxSize().background(color = backgroundColor))
120 }
121
122 @Composable
ControlPanelnull123 private fun ControlPanel(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
124 Column(modifier = modifier.fillMaxSize()) {
125 Spacer(modifier = Modifier.weight(2f))
126 Box(modifier = Modifier.weight(1f)) { content() }
127 }
128 }
129
130 @Composable
SignalControlnull131 private fun SignalControl(
132 modifier: Modifier = Modifier,
133 enabled: Boolean,
134 isStarted: Boolean,
135 onStartClick: () -> Unit = {},
<lambda>null136 onStopClick: () -> Unit = {},
137 ) {
138 val icon = if (isStarted) Icons.Filled.Close else Icons.Filled.PlayArrow
139
<lambda>null140 Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
141 AdvancedFloatingActionButton(
142 enabled = enabled,
143 onClick = if (isStarted) onStopClick else onStartClick,
144 backgroundColor = Color.Cyan
145 ) {
146 Icon(icon, stringResource(R.string.desc_signal_control))
147 }
148 }
149 }
150
151 @Composable
RecordingControlnull152 private fun RecordingControl(
153 modifier: Modifier = Modifier,
154 enabled: Boolean,
155 isStarted: Boolean,
156 isPaused: Boolean = false,
157 onStartClick: () -> Unit = {},
<lambda>null158 onStopClick: () -> Unit = {},
<lambda>null159 onPauseClick: () -> Unit = {},
<lambda>null160 onResumeClick: () -> Unit = {},
161 ) {
162 val startStopIconRes = if (isStarted) R.drawable.ic_stop else R.drawable.ic_record
163 val pauseResumeIconRes = if (isPaused) R.drawable.ic_record else R.drawable.ic_pause
164
<lambda>null165 Row(modifier = modifier.fillMaxSize()) {
166 Box(
167 modifier = modifier.weight(1f).fillMaxHeight(),
168 contentAlignment = Alignment.Center,
169 ) {
170 AdvancedFloatingActionButton(
171 enabled = enabled,
172 onClick = if (isStarted) onStopClick else onStartClick,
173 backgroundColor = Color.Cyan
174 ) {
175 Icon(
176 painterResource(startStopIconRes),
177 stringResource(R.string.desc_recording_control),
178 modifier.size(16.dp)
179 )
180 }
181 }
182 Box(
183 modifier = modifier.weight(1f).fillMaxHeight(),
184 contentAlignment = Alignment.Center,
185 ) {
186 if (enabled && isStarted) {
187 AdvancedFloatingActionButton(
188 enabled = true,
189 onClick = if (isPaused) onResumeClick else onPauseClick,
190 backgroundColor = Color.Cyan
191 ) {
192 Icon(
193 painterResource(pauseResumeIconRes),
194 stringResource(R.string.desc_pause_control),
195 modifier.size(16.dp)
196 )
197 }
198 }
199 }
200 }
201 }
202
203 @Preview(showBackground = true)
204 @Composable
Preview_SignalGeneratorPagenull205 private fun Preview_SignalGeneratorPage() {
206 MainContent()
207 }
208