• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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
17 
18 import android.content.ContentResolver
19 import android.net.Uri
20 import androidx.compose.animation.AnimatedVisibility
21 import androidx.compose.animation.core.tween
22 import androidx.compose.animation.fadeIn
23 import androidx.compose.animation.fadeOut
24 import androidx.compose.foundation.layout.Arrangement
25 import androidx.compose.foundation.layout.Box
26 import androidx.compose.foundation.layout.Column
27 import androidx.compose.foundation.layout.IntrinsicSize
28 import androidx.compose.foundation.layout.Row
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.foundation.layout.fillMaxWidth
31 import androidx.compose.foundation.layout.height
32 import androidx.compose.foundation.layout.padding
33 import androidx.compose.foundation.layout.safeDrawingPadding
34 import androidx.compose.material.icons.Icons
35 import androidx.compose.material.icons.filled.CameraAlt
36 import androidx.compose.material.icons.filled.Videocam
37 import androidx.compose.material.icons.outlined.CameraAlt
38 import androidx.compose.material.icons.outlined.Videocam
39 import androidx.compose.material3.LocalContentColor
40 import androidx.compose.material3.LocalTextStyle
41 import androidx.compose.runtime.Composable
42 import androidx.compose.runtime.CompositionLocalProvider
43 import androidx.compose.runtime.LaunchedEffect
44 import androidx.compose.runtime.getValue
45 import androidx.compose.runtime.mutableStateOf
46 import androidx.compose.runtime.remember
47 import androidx.compose.runtime.setValue
48 import androidx.compose.ui.Alignment
49 import androidx.compose.ui.Modifier
50 import androidx.compose.ui.graphics.Color
51 import androidx.compose.ui.graphics.vector.rememberVectorPainter
52 import androidx.compose.ui.platform.LocalContext
53 import androidx.compose.ui.platform.testTag
54 import androidx.compose.ui.res.stringResource
55 import androidx.compose.ui.tooling.preview.Preview
56 import androidx.compose.ui.unit.dp
57 import androidx.compose.ui.unit.sp
58 import com.google.jetpackcamera.core.camera.VideoRecordingState
59 import com.google.jetpackcamera.feature.preview.CaptureButtonUiState
60 import com.google.jetpackcamera.feature.preview.CaptureModeToggleUiState
61 import com.google.jetpackcamera.feature.preview.DEFAULT_CAPTURE_BUTTON_STATE
62 import com.google.jetpackcamera.feature.preview.ElapsedTimeUiState
63 import com.google.jetpackcamera.feature.preview.FlashModeUiState
64 import com.google.jetpackcamera.feature.preview.MultipleEventsCutter
65 import com.google.jetpackcamera.feature.preview.PreviewMode
66 import com.google.jetpackcamera.feature.preview.PreviewUiState
67 import com.google.jetpackcamera.feature.preview.PreviewViewModel
68 import com.google.jetpackcamera.feature.preview.R
69 import com.google.jetpackcamera.feature.preview.StabilizationUiState
70 import com.google.jetpackcamera.feature.preview.quicksettings.ui.QuickSettingsIndicators
71 import com.google.jetpackcamera.feature.preview.quicksettings.ui.ToggleQuickSettingsButton
72 import com.google.jetpackcamera.feature.preview.ui.debug.DebugOverlayToggleButton
73 import com.google.jetpackcamera.settings.model.FlashMode
74 import com.google.jetpackcamera.settings.model.ImageOutputFormat
75 import com.google.jetpackcamera.settings.model.LensFacing
76 import com.google.jetpackcamera.settings.model.StabilizationMode
77 import com.google.jetpackcamera.settings.model.SystemConstraints
78 import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
79 import com.google.jetpackcamera.settings.model.VideoQuality
80 import kotlinx.coroutines.delay
81 
82 class ZoomLevelDisplayState(private val alwaysDisplay: Boolean = false) {
83     private var _showZoomLevel = mutableStateOf(alwaysDisplay)
84     val showZoomLevel: Boolean get() = _showZoomLevel.value
85 
86     suspend fun showZoomLevel() {
87         if (!alwaysDisplay) {
88             _showZoomLevel.value = true
89             delay(3000)
90             _showZoomLevel.value = false
91         }
92     }
93 }
94 
95 @Composable
CameraControlsOverlaynull96 fun CameraControlsOverlay(
97     previewUiState: PreviewUiState.Ready,
98     modifier: Modifier = Modifier,
99     zoomLevelDisplayState: ZoomLevelDisplayState = remember { ZoomLevelDisplayState() },
<lambda>null100     onNavigateToSettings: () -> Unit = {},
<lambda>null101     onFlipCamera: () -> Unit = {},
<lambda>null102     onChangeFlash: (FlashMode) -> Unit = {},
<lambda>null103     onChangeImageFormat: (ImageOutputFormat) -> Unit = {},
<lambda>null104     onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {},
<lambda>null105     onToggleQuickSettings: () -> Unit = {},
<lambda>null106     onToggleDebugOverlay: () -> Unit = {},
<lambda>null107     onToggleAudio: () -> Unit = {},
<lambda>null108     onSetPause: (Boolean) -> Unit = {},
109     onCaptureImageWithUri: (
110         ContentResolver,
111         Uri?,
112         Boolean,
113         (PreviewViewModel.ImageCaptureEvent, Int) -> Unit
_null114     ) -> Unit = { _, _, _, _ -> },
115     onStartVideoRecording: (
116         Uri?,
117         Boolean,
118         (PreviewViewModel.VideoCaptureEvent) -> Unit
_null119     ) -> Unit = { _, _, _ -> },
<lambda>null120     onStopVideoRecording: () -> Unit = {},
<lambda>null121     onImageWellClick: (uri: Uri?) -> Unit = {},
122     onLockVideoRecording: (Boolean) -> Unit
123 ) {
124     // Show the current zoom level for a short period of time, only when the level changes.
<lambda>null125     var firstRun by remember { mutableStateOf(true) }
<lambda>null126     LaunchedEffect(previewUiState.zoomScale) {
127         if (firstRun) {
128             firstRun = false
129         } else {
130             zoomLevelDisplayState.showZoomLevel()
131         }
132     }
133 
<lambda>null134     CompositionLocalProvider(LocalContentColor provides Color.White) {
135         Box(
136             modifier
137                 .safeDrawingPadding()
138                 .fillMaxSize()
139         ) {
140             if (previewUiState.videoRecordingState is VideoRecordingState.Inactive) {
141                 ControlsTop(
142                     modifier = Modifier
143                         .fillMaxWidth()
144                         .align(Alignment.TopCenter),
145                     isQuickSettingsOpen = previewUiState.quickSettingsIsOpen,
146                     isDebugMode = previewUiState.debugUiState.isDebugMode,
147                     onNavigateToSettings = onNavigateToSettings,
148                     onChangeFlash = onChangeFlash,
149                     onToggleQuickSettings = onToggleQuickSettings,
150                     onToggleDebugOverlay = onToggleDebugOverlay,
151                     stabilizationUiState = previewUiState.stabilizationUiState,
152                     videoQuality = previewUiState.videoQuality,
153                     flashModeUiState = previewUiState.flashModeUiState
154                 )
155             }
156 
157             ControlsBottom(
158                 modifier = Modifier
159                     // padding to avoid snackbar
160                     .padding(bottom = 60.dp)
161                     .fillMaxWidth()
162                     .align(Alignment.BottomCenter),
163                 previewUiState = previewUiState,
164                 zoomLevel = previewUiState.zoomScale,
165                 physicalCameraId = previewUiState.currentPhysicalCameraId,
166                 logicalCameraId = previewUiState.currentLogicalCameraId,
167                 showZoomLevel = zoomLevelDisplayState.showZoomLevel,
168                 isQuickSettingsOpen = previewUiState.quickSettingsIsOpen,
169                 systemConstraints = previewUiState.systemConstraints,
170                 videoRecordingState = previewUiState.videoRecordingState,
171                 onFlipCamera = onFlipCamera,
172                 onCaptureImageWithUri = onCaptureImageWithUri,
173                 onToggleQuickSettings = onToggleQuickSettings,
174                 onToggleAudio = onToggleAudio,
175                 onSetPause = onSetPause,
176                 onChangeImageFormat = onChangeImageFormat,
177                 onToggleWhenDisabled = onToggleWhenDisabled,
178                 onStartVideoRecording = onStartVideoRecording,
179                 onStopVideoRecording = onStopVideoRecording,
180                 onImageWellClick = onImageWellClick,
181                 onLockVideoRecording = onLockVideoRecording
182             )
183         }
184     }
185 }
186 
187 @Composable
ControlsTopnull188 private fun ControlsTop(
189     isQuickSettingsOpen: Boolean,
190     modifier: Modifier = Modifier,
191     isDebugMode: Boolean = false,
192     onNavigateToSettings: () -> Unit = {},
<lambda>null193     onChangeFlash: (FlashMode) -> Unit = {},
<lambda>null194     onToggleQuickSettings: () -> Unit = {},
<lambda>null195     onToggleDebugOverlay: () -> Unit = {},
196     stabilizationUiState: StabilizationUiState = StabilizationUiState.Disabled,
197     videoQuality: VideoQuality = VideoQuality.UNSPECIFIED,
198     flashModeUiState: FlashModeUiState = FlashModeUiState.Unavailable
199 ) {
<lambda>null200     Column(modifier) {
201         Row(modifier, verticalAlignment = Alignment.CenterVertically) {
202             Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
203                 // button to open default settings page
204                 SettingsNavButton(
205                     modifier = Modifier
206                         .padding(12.dp)
207                         .testTag(SETTINGS_BUTTON),
208                     onNavigateToSettings = onNavigateToSettings
209                 )
210                 AnimatedVisibility(
211                     visible = !isQuickSettingsOpen,
212                     enter = fadeIn(),
213                     exit = fadeOut()
214                 ) {
215                     QuickSettingsIndicators(
216                         flashModeUiState = flashModeUiState,
217                         onFlashModeClick = onChangeFlash
218                     )
219                 }
220             }
221 
222             // quick settings button
223             ToggleQuickSettingsButton(
224                 toggleDropDown = onToggleQuickSettings,
225                 isOpen = isQuickSettingsOpen
226             )
227 
228             Row(
229                 Modifier.weight(1f),
230                 verticalAlignment = Alignment.CenterVertically,
231                 horizontalArrangement = Arrangement.SpaceEvenly
232             ) {
233                 var visibleStabilizationUiState: StabilizationUiState by remember {
234                     mutableStateOf(StabilizationUiState.Disabled)
235                 }
236                 if (stabilizationUiState is StabilizationUiState.Enabled) {
237                     // Only save StabilizationUiState.Set so exit transition can happen properly
238                     visibleStabilizationUiState = stabilizationUiState
239                 }
240                 AnimatedVisibility(
241                     visible = stabilizationUiState is StabilizationUiState.Enabled,
242                     enter = fadeIn(),
243                     exit = fadeOut()
244                 ) {
245                     (visibleStabilizationUiState as? StabilizationUiState.Enabled)?.let {
246                         StabilizationIcon(stabilizationUiState = it)
247                     }
248                 }
249                 VideoQualityIcon(videoQuality, Modifier.testTag(VIDEO_QUALITY_TAG))
250             }
251         }
252         if (isDebugMode) {
253             DebugOverlayToggleButton(toggleIsOpen = onToggleDebugOverlay)
254         }
255     }
256 }
257 
258 @Composable
ControlsBottomnull259 private fun ControlsBottom(
260     modifier: Modifier = Modifier,
261     previewUiState: PreviewUiState.Ready,
262     physicalCameraId: String? = null,
263     logicalCameraId: String? = null,
264     zoomLevel: Float,
265     showZoomLevel: Boolean,
266     isQuickSettingsOpen: Boolean,
267     systemConstraints: SystemConstraints,
268     videoRecordingState: VideoRecordingState,
269     onFlipCamera: () -> Unit = {},
270     onCaptureImageWithUri: (
271         ContentResolver,
272         Uri?,
273         Boolean,
274         (PreviewViewModel.ImageCaptureEvent, Int) -> Unit
_null275     ) -> Unit = { _, _, _, _ -> },
<lambda>null276     onToggleQuickSettings: () -> Unit = {},
<lambda>null277     onToggleAudio: () -> Unit = {},
<lambda>null278     onSetPause: (Boolean) -> Unit = {},
<lambda>null279     onChangeImageFormat: (ImageOutputFormat) -> Unit = {},
<lambda>null280     onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit = {},
281     onStartVideoRecording: (
282         Uri?,
283         Boolean,
284         (PreviewViewModel.VideoCaptureEvent) -> Unit
_null285     ) -> Unit = { _, _, _ -> },
<lambda>null286     onStopVideoRecording: () -> Unit = {},
<lambda>null287     onImageWellClick: (uri: Uri?) -> Unit = {},
<lambda>null288     onLockVideoRecording: (Boolean) -> Unit = {}
289 ) {
290     Column(
291         modifier = modifier,
292         horizontalAlignment = Alignment.CenterHorizontally
293     ) {
294         CompositionLocalProvider(
295             LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 20.sp)
<lambda>null296         ) {
297             Column(horizontalAlignment = Alignment.CenterHorizontally) {
298                 if (showZoomLevel) {
299                     ZoomScaleText(zoomLevel)
300                 }
301                 if (previewUiState.debugUiState.isDebugMode) {
302                     CurrentCameraIdText(physicalCameraId, logicalCameraId)
303                 }
304                 if (previewUiState.elapsedTimeUiState is ElapsedTimeUiState.Enabled) {
305                     AnimatedVisibility(
306                         visible = (
307                             previewUiState.videoRecordingState is
308                                 VideoRecordingState.Active
309                             ),
310                         enter = fadeIn(),
311                         exit = fadeOut(animationSpec = tween(delayMillis = 1_500))
312                     ) {
313                         ElapsedTimeText(
314                             modifier = Modifier.testTag(ELAPSED_TIME_TAG),
315                             elapsedTimeUiState = previewUiState.elapsedTimeUiState
316                         )
317                     }
318                 }
319             }
320         }
321 
322         Column {
323             if (!isQuickSettingsOpen &&
324                 previewUiState.captureModeToggleUiState
325                     is CaptureModeToggleUiState.Visible
326             ) {
327                 // TODO(yasith): Align to end of ImageWell based on alignment lines
328                 Box(
329                     Modifier.align(Alignment.End).padding(end = 12.dp)
<lambda>null330                 ) {
331                     CaptureModeToggleButton(
332                         uiState = previewUiState.captureModeToggleUiState,
333                         onChangeImageFormat = onChangeImageFormat,
334                         onToggleWhenDisabled = onToggleWhenDisabled,
335                         modifier = Modifier.testTag(CAPTURE_MODE_TOGGLE_BUTTON)
336                     )
337                 }
338             }
339 
340             Row(
341                 Modifier
342                     .fillMaxWidth()
343                     .height(IntrinsicSize.Max),
344                 verticalAlignment = Alignment.CenterVertically
<lambda>null345             ) {
346                 // Row that holds flip camera, capture button, and audio
347                 Row(Modifier.weight(1f), horizontalArrangement = Arrangement.SpaceEvenly) {
348                     // animation fades in/out this component based on quick settings
349                     AnimatedVisibility(
350                         visible = !isQuickSettingsOpen,
351                         enter = fadeIn(),
352                         exit = fadeOut()
353                     ) {
354                         if (videoRecordingState is VideoRecordingState.Inactive) {
355                             FlipCameraButton(
356                                 modifier = Modifier.testTag(FLIP_CAMERA_BUTTON),
357                                 onClick = onFlipCamera,
358                                 lensFacing = previewUiState.currentCameraSettings.cameraLensFacing,
359                                 // enable only when phone has front and rear camera
360                                 enabledCondition = systemConstraints.availableLenses.size > 1
361                             )
362                         } else if (videoRecordingState is VideoRecordingState.Active
363                         ) {
364                             PauseResumeToggleButton(
365                                 onSetPause = onSetPause,
366                                 currentRecordingState = videoRecordingState
367                             )
368                         }
369                     }
370                 }
371                 CaptureButton(
372                     captureButtonUiState = previewUiState.captureButtonUiState,
373                     previewMode = previewUiState.previewMode,
374                     isQuickSettingsOpen = isQuickSettingsOpen,
375                     onCaptureImageWithUri = onCaptureImageWithUri,
376                     onToggleQuickSettings = onToggleQuickSettings,
377                     onStartVideoRecording = onStartVideoRecording,
378                     onStopVideoRecording = onStopVideoRecording,
379                     onLockVideoRecording = onLockVideoRecording
380                 )
381                 Row(Modifier.weight(1f), horizontalArrangement = Arrangement.SpaceEvenly) {
382                     if (videoRecordingState is VideoRecordingState.Active) {
383                         AmplitudeVisualizer(
384                             modifier = Modifier
385                                 .weight(1f)
386                                 .fillMaxSize(),
387                             onToggleAudio = onToggleAudio,
388                             audioUiState = previewUiState.audioUiState
389                         )
390                     } else {
391                         Column {
392                             if (!isQuickSettingsOpen &&
393                                 previewUiState.previewMode is PreviewMode.StandardMode
394                             ) {
395                                 ImageWell(
396                                     modifier = Modifier.weight(1f),
397                                     imageWellUiState = previewUiState.imageWellUiState,
398                                     onClick = onImageWellClick
399                                 )
400                             }
401                         }
402                     }
403                 }
404             }
405         }
406     }
407 }
408 
409 @Composable
CaptureButtonnull410 private fun CaptureButton(
411     modifier: Modifier = Modifier,
412     captureButtonUiState: CaptureButtonUiState,
413     isQuickSettingsOpen: Boolean,
414     previewMode: PreviewMode,
415     onToggleQuickSettings: () -> Unit = {},
416     onCaptureImageWithUri: (
417         ContentResolver,
418         Uri?,
419         Boolean,
420         (PreviewViewModel.ImageCaptureEvent, Int) -> Unit
_null421     ) -> Unit = { _, _, _, _ -> },
422     onStartVideoRecording: (
423         Uri?,
424         Boolean,
425         (PreviewViewModel.VideoCaptureEvent) -> Unit
_null426     ) -> Unit = { _, _, _ -> },
<lambda>null427     onStopVideoRecording: () -> Unit = {},
<lambda>null428     onLockVideoRecording: (Boolean) -> Unit = {}
429 ) {
<lambda>null430     val multipleEventsCutter = remember { MultipleEventsCutter() }
431     val context = LocalContext.current
432 
433     CaptureButton(
434         modifier = modifier.testTag(CAPTURE_BUTTON),
<lambda>null435         onCaptureImage = {
436             if (captureButtonUiState is CaptureButtonUiState.Enabled) {
437                 multipleEventsCutter.processEvent {
438                     when (previewMode) {
439                         is PreviewMode.StandardMode -> {
440                             onCaptureImageWithUri(
441                                 context.contentResolver,
442                                 null,
443                                 true
444                             ) { event: PreviewViewModel.ImageCaptureEvent, _: Int ->
445                                 previewMode.onImageCapture(event)
446                             }
447                         }
448 
449                         is PreviewMode.ExternalImageCaptureMode -> {
450                             onCaptureImageWithUri(
451                                 context.contentResolver,
452                                 previewMode.imageCaptureUri,
453                                 false
454                             ) { event: PreviewViewModel.ImageCaptureEvent, _: Int ->
455                                 previewMode.onImageCapture(event)
456                             }
457                         }
458 
459                         is PreviewMode.ExternalMultipleImageCaptureMode -> {
460                             val ignoreUri =
461                                 previewMode.imageCaptureUris.isNullOrEmpty()
462                             onCaptureImageWithUri(
463                                 context.contentResolver,
464                                 null,
465                                 previewMode.imageCaptureUris.isNullOrEmpty() ||
466                                     ignoreUri,
467                                 previewMode.onImageCapture
468                             )
469                         }
470 
471                         else -> {
472                             onCaptureImageWithUri(
473                                 context.contentResolver,
474                                 null,
475                                 false
476                             ) { _: PreviewViewModel.ImageCaptureEvent, _: Int -> }
477                         }
478                     }
479                 }
480             }
481             if (isQuickSettingsOpen) {
482                 onToggleQuickSettings()
483             }
484         },
<lambda>null485         onStartVideoRecording = {
486             if (captureButtonUiState is CaptureButtonUiState.Enabled) {
487                 when (previewMode) {
488                     is PreviewMode.StandardMode -> {
489                         onStartVideoRecording(null, false) {}
490                     }
491 
492                     is PreviewMode.ExternalVideoCaptureMode -> {
493                         onStartVideoRecording(
494                             previewMode.videoCaptureUri,
495                             true,
496                             previewMode.onVideoCapture
497                         )
498                     }
499 
500                     else -> {
501                         onStartVideoRecording(null, false) {}
502                     }
503                 }
504                 if (isQuickSettingsOpen) {
505                     onToggleQuickSettings()
506                 }
507             }
508         },
<lambda>null509         onStopVideoRecording = {
510             onStopVideoRecording()
511         },
512         captureButtonUiState = captureButtonUiState,
513         onLockVideoRecording = onLockVideoRecording
514     )
515 }
516 
517 @Composable
CaptureModeToggleButtonnull518 private fun CaptureModeToggleButton(
519     uiState: CaptureModeToggleUiState.Visible,
520     onChangeImageFormat: (ImageOutputFormat) -> Unit,
521     onToggleWhenDisabled: (CaptureModeToggleUiState.DisabledReason) -> Unit,
522     modifier: Modifier = Modifier
523 ) {
524     // Captures hdr image (left) when output format is UltraHdr, else captures hdr video (right).
525     val initialState =
526         when (uiState.currentMode) {
527             CaptureModeToggleUiState.ToggleMode.CAPTURE_TOGGLE_IMAGE -> ToggleState.Left
528             CaptureModeToggleUiState.ToggleMode.CAPTURE_TOGGLE_VIDEO -> ToggleState.Right
529         }
530     ToggleButton(
531         leftIcon = if (uiState.currentMode ==
532             CaptureModeToggleUiState.ToggleMode.CAPTURE_TOGGLE_IMAGE
533         ) {
534             rememberVectorPainter(image = Icons.Filled.CameraAlt)
535         } else {
536             rememberVectorPainter(image = Icons.Outlined.CameraAlt)
537         },
538         rightIcon = if (uiState.currentMode ==
539             CaptureModeToggleUiState.ToggleMode.CAPTURE_TOGGLE_VIDEO
540         ) {
541             rememberVectorPainter(image = Icons.Filled.Videocam)
542         } else {
543             rememberVectorPainter(image = Icons.Outlined.Videocam)
544         },
545         initialState = initialState,
546         onToggleStateChanged = {
547             val imageFormat = when (it) {
548                 ToggleState.Left -> ImageOutputFormat.JPEG_ULTRA_HDR
549                 ToggleState.Right -> ImageOutputFormat.JPEG
550             }
551             onChangeImageFormat(imageFormat)
552         },
553         onToggleWhenDisabled = {
554             check(uiState is CaptureModeToggleUiState.Disabled)
555             onToggleWhenDisabled(uiState.disabledReason)
556         },
557         enabled = uiState is CaptureModeToggleUiState.Enabled,
558         leftIconDescription =
559         stringResource(id = R.string.capture_mode_image_capture_content_description),
560         rightIconDescription =
561         stringResource(id = R.string.capture_mode_video_recording_content_description),
562         modifier = modifier
563     )
564 }
565 
566 @Preview(backgroundColor = 0xFF000000, showBackground = true)
567 @Composable
Preview_ControlsTop_QuickSettingsOpennull568 private fun Preview_ControlsTop_QuickSettingsOpen() {
569     CompositionLocalProvider(LocalContentColor provides Color.White) {
570         ControlsTop(
571             isQuickSettingsOpen = true
572         )
573     }
574 }
575 
576 @Preview(backgroundColor = 0xFF000000, showBackground = true)
577 @Composable
Preview_ControlsTop_QuickSettingsClosednull578 private fun Preview_ControlsTop_QuickSettingsClosed() {
579     CompositionLocalProvider(LocalContentColor provides Color.White) {
580         ControlsTop(
581             isQuickSettingsOpen = false
582         )
583     }
584 }
585 
586 @Preview(backgroundColor = 0xFF000000, showBackground = true)
587 @Composable
Preview_ControlsTop_FlashModeOnnull588 private fun Preview_ControlsTop_FlashModeOn() {
589     CompositionLocalProvider(LocalContentColor provides Color.White) {
590         ControlsTop(
591             isQuickSettingsOpen = false,
592             flashModeUiState = FlashModeUiState.Available(
593                 selectedFlashMode = FlashMode.ON,
594                 availableFlashModes = listOf(FlashMode.OFF, FlashMode.ON),
595                 isActive = false
596             )
597         )
598     }
599 }
600 
601 @Preview(backgroundColor = 0xFF000000, showBackground = true)
602 @Composable
Preview_ControlsTop_FlashModeAutonull603 private fun Preview_ControlsTop_FlashModeAuto() {
604     CompositionLocalProvider(LocalContentColor provides Color.White) {
605         ControlsTop(
606             isQuickSettingsOpen = false,
607             flashModeUiState = FlashModeUiState.Available(
608                 selectedFlashMode = FlashMode.AUTO,
609                 availableFlashModes = listOf(FlashMode.OFF, FlashMode.ON, FlashMode.AUTO),
610                 isActive = false
611             )
612         )
613     }
614 }
615 
616 @Preview(backgroundColor = 0xFF000000, showBackground = true)
617 @Composable
Preview_ControlsTop_WithStabilizationnull618 private fun Preview_ControlsTop_WithStabilization() {
619     CompositionLocalProvider(LocalContentColor provides Color.White) {
620         ControlsTop(
621             isQuickSettingsOpen = false,
622             stabilizationUiState = StabilizationUiState.Specific(
623                 stabilizationMode = StabilizationMode.ON
624             )
625         )
626     }
627 }
628 
629 @Preview(backgroundColor = 0xFF000000, showBackground = true)
630 @Composable
Preview_ControlsTop_WithStabilizationAutonull631 private fun Preview_ControlsTop_WithStabilizationAuto() {
632     CompositionLocalProvider(LocalContentColor provides Color.White) {
633         ControlsTop(
634             isQuickSettingsOpen = false,
635             stabilizationUiState = StabilizationUiState.Auto(
636                 stabilizationMode = StabilizationMode.OPTICAL
637             )
638         )
639     }
640 }
641 
642 @Preview(backgroundColor = 0xFF000000, showBackground = true)
643 @Composable
Preview_ControlsBottomnull644 private fun Preview_ControlsBottom() {
645     CompositionLocalProvider(LocalContentColor provides Color.White) {
646         ControlsBottom(
647             previewUiState = PreviewUiState.Ready(
648                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
649                 previewMode = PreviewMode.StandardMode {},
650                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
651                 videoRecordingState = VideoRecordingState.Inactive(),
652                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
653             ),
654             zoomLevel = 1.3f,
655             showZoomLevel = true,
656             isQuickSettingsOpen = false,
657             systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
658             videoRecordingState = VideoRecordingState.Inactive()
659         )
660     }
661 }
662 
663 @Preview(backgroundColor = 0xFF000000, showBackground = true)
664 @Composable
Preview_ControlsBottom_NoZoomLevelnull665 private fun Preview_ControlsBottom_NoZoomLevel() {
666     CompositionLocalProvider(LocalContentColor provides Color.White) {
667         ControlsBottom(
668             previewUiState = PreviewUiState.Ready(
669                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
670                 previewMode = PreviewMode.StandardMode {},
671                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
672                 videoRecordingState = VideoRecordingState.Inactive(),
673                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
674             ),
675             zoomLevel = 1.3f,
676             showZoomLevel = false,
677             isQuickSettingsOpen = false,
678             systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
679             videoRecordingState = VideoRecordingState.Inactive()
680         )
681     }
682 }
683 
684 @Preview(backgroundColor = 0xFF000000, showBackground = true)
685 @Composable
Preview_ControlsBottom_QuickSettingsOpennull686 private fun Preview_ControlsBottom_QuickSettingsOpen() {
687     CompositionLocalProvider(LocalContentColor provides Color.White) {
688         ControlsBottom(
689             previewUiState = PreviewUiState.Ready(
690                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
691                 previewMode = PreviewMode.StandardMode {},
692                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
693                 videoRecordingState = VideoRecordingState.Inactive(),
694                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
695             ),
696             zoomLevel = 1.3f,
697             showZoomLevel = true,
698             isQuickSettingsOpen = true,
699             systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
700             videoRecordingState = VideoRecordingState.Inactive()
701         )
702     }
703 }
704 
705 @Preview(backgroundColor = 0xFF000000, showBackground = true)
706 @Composable
Preview_ControlsBottom_NoFlippableCameranull707 private fun Preview_ControlsBottom_NoFlippableCamera() {
708     CompositionLocalProvider(LocalContentColor provides Color.White) {
709         ControlsBottom(
710             previewUiState = PreviewUiState.Ready(
711                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
712                 previewMode = PreviewMode.StandardMode {},
713                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
714                 videoRecordingState = VideoRecordingState.Inactive(),
715                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
716             ),
717             zoomLevel = 1.3f,
718             showZoomLevel = true,
719             isQuickSettingsOpen = false,
720             systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS.copy(
721                 availableLenses = listOf(LensFacing.FRONT),
722                 perLensConstraints = mapOf(
723                     LensFacing.FRONT to
724                         TYPICAL_SYSTEM_CONSTRAINTS.perLensConstraints[LensFacing.FRONT]!!
725                 )
726             ),
727             videoRecordingState = VideoRecordingState.Inactive()
728         )
729     }
730 }
731 
732 @Preview(backgroundColor = 0xFF000000, showBackground = true)
733 @Composable
Preview_ControlsBottom_Recordingnull734 private fun Preview_ControlsBottom_Recording() {
735     CompositionLocalProvider(LocalContentColor provides Color.White) {
736         ControlsBottom(
737             previewUiState = PreviewUiState.Ready(
738                 systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
739                 previewMode = PreviewMode.StandardMode {},
740                 captureModeToggleUiState = CaptureModeToggleUiState.Invisible,
741                 videoRecordingState = VideoRecordingState.Active.Recording(0L, .9, 1_000_000_000),
742                 captureButtonUiState = DEFAULT_CAPTURE_BUTTON_STATE
743             ),
744             zoomLevel = 1.3f,
745             showZoomLevel = true,
746             isQuickSettingsOpen = false,
747             systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS,
748             videoRecordingState = VideoRecordingState.Active.Recording(0L, .9, 1_000_000_000)
749         )
750     }
751 }
752