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