Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ private fun CaptureKeyHandler(
}
}

/**
* A capture button that can be used for both image and video capture.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description seems a little sparse. What are the different modes it can be in? What about the lock behavior?

*
* @param modifier the modifier for this component
* @param onImageCapture the callback for an image capture event
* @param onStartRecording the callback for a start recording event
* @param onStopRecording the callback for a stop recording event
* @param onLockVideoRecording The callback for a lock video recording event. The boolean parameter indicates if the recording should be locked.
* @param onIncrementZoom The callback for a zoom increment event, providing the zoom increment value.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: explain why there is a zoom option here. It seems out of place without explanation.

* @param captureButtonUiState the [CaptureButtonUiState] for this component
* @param captureButtonSize the size of the capture button
*/
@Composable
internal fun CaptureButton(
modifier: Modifier = Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,25 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

// these layouts are only concerned with placement. nothing else. no state handling
/**
* The base layout for the camera capture screen.
*
* @param modifier the modifier for this component
* @param viewfinder the viewfinder composable
* @param captureButton the capture button composable
* @param imageWell the image well composable
* @param flipCameraButton the flip camera button composable
* @param zoomLevelDisplay the zoom level display composable
* @param elapsedTimeDisplay the elapsed time display composable
* @param quickSettingsButton the quick settings button composable
* @param indicatorRow the indicator row composable
* @param captureModeToggle the capture mode toggle composable
* @param quickSettingsOverlay the quick settings overlay composable
* @param debugOverlay the debug overlay composable
* @param debugVisibilityWrapper A wrapper that conditionally hides its contents based on debug settings
* @param screenFlashOverlay the screen flash overlay composable
* @param snackBar the snack bar composable for showing messages
*/
@Composable
fun PreviewLayout(
modifier: Modifier = Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ private const val BLINK_TIME = 100L
private val TAP_TO_FOCUS_INDICATOR_SIZE = 56.dp
private const val FOCUS_INDICATOR_RESULT_DELAY = 100L

/**
* A composable that displays the elapsed time since the start of a video recording.
*
* @param elapsedTimeUiState the [ElapsedTimeUiState] for this component
*/
@Composable
fun ElapsedTimeText(modifier: Modifier = Modifier, elapsedTimeUiState: ElapsedTimeUiState) {
if (elapsedTimeUiState is ElapsedTimeUiState.Enabled) {
Expand All @@ -146,6 +151,14 @@ fun ElapsedTimeText(modifier: Modifier = Modifier, elapsedTimeUiState: ElapsedTi
}
}

/**
* A composable that displays a toggle button for pausing and resuming video recording.
*
* @param modifier the modifier for this component
* @param onSetPause the callback for setting the pause state
* @param size the size of the button
* @param currentRecordingState the current recording state
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun PauseResumeToggleButton(
Expand Down Expand Up @@ -177,6 +190,14 @@ fun PauseResumeToggleButton(
}
}

/**
* A composable that displays a toggle button for enabling and disabling audio.
*
* @param modifier the modifier for this component
* @param buttonSize the size of the button
* @param audioUiState the [AudioUiState] for this component
* @param onToggleAudio the callback for toggling audio
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AmplitudeToggleButton(
Expand Down Expand Up @@ -235,6 +256,14 @@ fun AmplitudeToggleButton(
}
}

/**
* A composable that displays a toggle button for switching between image and video capture.
*
* @param uiState the [CaptureModeToggleUiState.Available] for this component
* @param onChangeCaptureMode the callback for changing the capture mode
* @param onToggleWhenDisabled the callback for when the toggle is disabled
* @param modifier the modifier for this component
*/
@Composable
fun CaptureModeToggleButton(
uiState: CaptureModeToggleUiState.Available,
Expand Down Expand Up @@ -304,6 +333,14 @@ fun CaptureModeToggleButton(
)
}

/**
* A composable that displays a snackbar with a test tag.
*
* @param modifier the modifier for this component
* @param snackbarToShow the [SnackbarData] to show
* @param snackbarHostState the [SnackbarHostState] for this component
* @param onSnackbarResult the callback for the snackbar result
*/
@Composable
fun TestableSnackbar(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -510,6 +547,19 @@ fun PreviewDisplay(
}
}

/**
* A composable that displays the capture button.
*
* @param modifier the modifier for this component
* @param captureButtonUiState the [CaptureButtonUiState] for this component
* @param isQuickSettingsOpen true if the quick settings are open
* @param onToggleQuickSettings the callback for toggling the quick settings
* @param onIncrementZoom A callback to increment the zoom, providing the zoom increment value.
* @param onCaptureImage A callback to capture an image, providing the `ContentResolver` for saving.
* @param onStartVideoRecording The callback for starting a video recording.
* @param onStopVideoRecording The callback for stopping a video recording.
* @param onLockVideoRecording A callback to lock video recording. The boolean parameter indicates if the recording should be locked.
*/
@Composable
fun CaptureButton(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -548,6 +598,12 @@ fun CaptureButton(
)
}

/**
* A composable that displays the stabilization icon.
*
* @param stabilizationUiState the [StabilizationUiState] for this component
* @param modifier the modifier for this component
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun StabilizationIcon(stabilizationUiState: StabilizationUiState, modifier: Modifier = Modifier) {
Expand Down Expand Up @@ -625,6 +681,12 @@ fun StabilizationIcon(stabilizationUiState: StabilizationUiState, modifier: Modi
}
}

/**
* A composable that displays the video quality icon.
*
* @param videoQuality the [VideoQuality] for this component
* @param modifier the modifier for this component
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun VideoQualityIcon(videoQuality: VideoQuality, modifier: Modifier = Modifier) {
Expand Down Expand Up @@ -671,6 +733,14 @@ fun VideoQualityIcon(videoQuality: VideoQuality, modifier: Modifier = Modifier)
}
}

/**
* A composable that displays the flip camera button.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few composables that are documented like this. I think you should add a little more about the behavior of each composable. This description is already basically captured by virtue of the signature:

@Composable
fun FlipCameraButton(

*
* @param enabledCondition the enabled condition for this component
* @param flipLensUiState the [FlipLensUiState] for this component
* @param onClick the callback for when the button is clicked
* @param modifier the modifier for this component
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun FlipCameraButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ import kotlinx.coroutines.flow.runningFold
/** Orientation hysteresis amount used in rounding, in degrees. */
private const val ORIENTATION_HYSTERESIS = 5

/**
* A flow that emits the device's orientation, debounced to avoid rapid changes.
*
* @param context the application context
*/
fun debouncedOrientationFlow(context: Context) = callbackFlow {
val orientationListener = object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ package com.google.jetpackcamera.ui.components.capture

import com.google.jetpackcamera.ui.uistate.DisableRationale

/**
* Represents reasons why a UI component or functionality might be disabled, providing a test tag
* and a string resource ID for user-facing explanation.
*/
enum class DisabledReason(
// 'override' is required
override val testTag: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@
package com.google.jetpackcamera.ui.components.capture

/**
* A helper class that prevents multiple clicks.
* A helper class that debounces events, preventing multiple rapid-fire executions.
*/
internal class MultipleEventsCutter {
internal class EventDebouncer {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think this class is used currently. It can probably be deleted.

private val now: Long
get() = System.currentTimeMillis()

private var lastEventTimeMs: Long = 0

/**
* Processes an event, executing it only if a specified duration has passed since the last
* event. This prevents multiple rapid-fire executions of the same event.
*
* @param event The lambda function representing the event to be executed.
*/
fun processEvent(event: () -> Unit) {
if (now - lastEventTimeMs >= DURATION_BETWEEN_CLICKS_MS) {
event.invoke()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ import androidx.core.graphics.createBitmap
import com.google.jetpackcamera.data.media.MediaDescriptor
import com.google.jetpackcamera.ui.uistate.capture.ImageWellUiState

/**
* A composable that displays the last captured image.
*
* @param imageWellUiState the [ImageWellUiState.LastCapture] for this component
* @param onClick the callback for when the image well is clicked
* @param modifier the modifier for this component
* @param shape the shape of the image well
* @param enabled true if the image well is enabled
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalAnimationApi::class)
@Composable
fun ImageWell(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ import com.google.jetpackcamera.ui.uistate.capture.ScreenFlashUiState

private const val TAG = "ScreenFlashComponents"

/**
* A composable that manages the screen flash UI and brightness.
*
* @param screenFlashUiState The [ScreenFlashUiState] that controls the screen flash behavior.
* @param onInitialBrightnessCalculated Callback to provide the initial screen brightness when it's first calculated.
*/
@Composable
fun ScreenFlashScreen(
screenFlashUiState: ScreenFlashUiState,
Expand All @@ -55,6 +61,12 @@ fun ScreenFlashScreen(
}
}

/**
* A composable that displays a white overlay to simulate a screen flash.
*
* @param screenFlashUiState The [ScreenFlashUiState] that controls the overlay's visibility and behavior.
* @param modifier The modifier for this composable.
*/
@Composable
private fun ScreenFlashOverlay(
screenFlashUiState: ScreenFlashUiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ import com.google.jetpackcamera.ui.uistate.capture.ZoomControlUiState
import java.math.RoundingMode
import java.text.DecimalFormat

/**
* A composable that displays a row of zoom buttons.
*
* @param onChangeZoom the callback for when the zoom changes
* @param modifier the modifier for this component
* @param zoomControlUiState the [ZoomControlUiState] for this component
* @param buttonSize the size of the buttons
* @param spacing the spacing between the buttons
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ZoomButtonRow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,31 @@ class ZoomState(
}

/**
* Scale the current zoom level.
* Scales the current zoom level by the given [scalingFactor].
*
* @param scalingFactor The factor by which to scale the current zoom level.
* @param lensToZoom Specifies which lens's zoom to modify (primary or secondary).
*/
suspend fun scaleZoom(scalingFactor: Float, lensToZoom: LensToZoom) {
absoluteZoom(scalingFactor * functionalZoom, lensToZoom)
}

/**
* Increment the current zoom level.
* Increments the current zoom level by the given [increment].
*
* @param increment The amount to increment the current zoom level by.
* @param lensToZoom Specifies which lens's zoom to modify (primary or secondary).
*/
suspend fun incrementZoom(increment: Float, lensToZoom: LensToZoom) {
absoluteZoom(increment + functionalZoom, lensToZoom)
}

/**
* Ease towards a specific zoom level
* Animates the zoom level to the specified [targetZoomLevel].
*
* @param animationSpec [androidx.compose.animation.core.AnimationSpec] used for the animation, default to tween over 500ms
* @param targetZoomLevel The target zoom level to animate to.
* @param animationSpec The [AnimationSpec] to use for the animation.
* @param lensToZoom Specifies which lens's zoom to modify (primary or secondary).
*/
suspend fun animatedZoom(
targetZoomLevel: Float,
Expand Down Expand Up @@ -113,6 +121,12 @@ class ZoomState(
}
}

/**
* Updates the zoom state when the camera lens changes.
*
* @param newInitialZoomLevel The new initial zoom level.
* @param newZoomRange The new zoom range.
*/
suspend fun onChangeLens(newInitialZoomLevel: Float, newZoomRange: Range<Float>) {
mutatorMutex.mutate(MutatePriority.PreventUserInput) {
functionalZoom = newInitialZoomLevel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,15 @@ private fun QuickSettingToggleButton(
)
}

/**
* A modal bottom sheet composable used to display a collection of quick setting buttons.
*
* @param modifier The [Modifier] to be applied to this composable.
* @param onDismiss The lambda function to be invoked when the bottom sheet is dismissed.
* @param sheetState The [SheetState] controlling the visibility and behavior of the bottom sheet.
* @param quickSettingButtons A variable number of composable functions that represent the buttons
* to be displayed within the quick settings bottom sheet.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun QuickSettingsBottomSheet(
Expand Down Expand Up @@ -684,11 +693,17 @@ private fun QuickSettingsBottomSheetRow(
}

/**
* @param isHighlighted true if the button is currently checked; false otherwise.
* @param onClick will be called when the user clicks the button.
* @param text The text label to display below the icon.
* @param painter The icon to display inside the button.
* @param modifier The Modifier to be applied to the button.
* A customizable toggle button used within the quick settings menu. This button displays an icon
* and a text label, and can be highlighted to indicate a selected state. It serves as a generic
* component for various quick settings options.
*
* @param onClick The lambda function to be invoked when the button is clicked.
* @param text The text label displayed below the icon.
* @param accessibilityText The content description for accessibility purposes.
* @param painter The [Painter] for the icon displayed inside the button.
* @param modifier The [Modifier] to be applied to this composable.
* @param isHighlighted A boolean indicating whether the button is currently in a highlighted (selected) state.
* @param enabled A boolean indicating whether the button is interactive.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

/**
* A composable that provides a Material Design theme for previews within the Jetpack Camera App.
*
* This theme adapts to system dark mode settings and supports dynamic colors on Android 12+.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is technically true, but I don't think we'll need to support the light theme in the future. No need to change anything now, except for maybe adding a TODO to remove the ability to change the theme.

*
* @param darkTheme Whether the theme should be in dark mode. Defaults to `isSystemInDarkTheme()`.
* @param dynamicColor Whether dynamic color should be enabled. Defaults to `true`.
* @param content The composable content to which the theme will be applied.
*/
@Composable
fun PreviewPreviewTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
Expand Down
Loading