Skip to content

Update CaptureButton sizing, colors, and Material 3 Expressive motion specs - #578

Open
temcguir wants to merge 6 commits into
mainfrom
temcguir/capture_button_ring_cleanup
Open

temcguir wants to merge 6 commits into
mainfrom
temcguir/capture_button_ring_cleanup

Conversation

@temcguir

Copy link
Copy Markdown
Collaborator

This PR updates CaptureButton, CaptureButtonRing, and CaptureButtonNucleus to use Material 3 Expressive motion specifications and refined sizing and geometry.

Key Changes

  • Button and Ring Sizing:

    • Updated default capture button diameter from 76.dp to 86.dp.
    • Animated outer ring stroke width (3.dp <-> 0.dp) using MotionScheme.expressive().fastSpatialSpec() so the outer border smoothly transitions when entering or leaving CaptureMode.STANDARD.
    • Added KDoc documentation to CaptureButtonRing and keyed isCaptureButtonPressed with remember(initialPressed) to properly support LocalInitialPressedState.
  • Spatial and Effects Motion Specs:

    • Updated CaptureButtonNucleus size and corner radius animations to use MotionScheme.expressive().fastSpatialSpec(), synchronizing the scale transition and corner radius morph (8.dp when locked).
    • Replaced fixed-duration color transitions with MotionScheme.expressive().fastEffectsSpec(), updated recording indicator color to #ED0000, and updated pressed opacity in CaptureMode.IMAGE_ONLY from 0.5f to 0.8f.
  • Standard (Hybrid) Mode Transitions:

    • Added latent idle scale (0.80f) and pressed capture scale (0.86f) with a responsive spring spec (stiffness = 1800f, dampingRatio = 0.65f), instant color materialization, and a 50ms visual press hold for quick taps so taps reliably materialize and bounce within the outer ring border.
    • Configured stopping video recording in standard mode to expand smoothly outward instead of collapsing to 0.dp.
  • Testing:

    • Added unit tests verifying outer ring border visibility across capture modes and recording states.
    • Added unit tests verifying spring scale dynamics, disabled animation behavior, and image-only nucleus scaling.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the CaptureButton animations to utilize Material 3 Expressive Motion schemes, replacing hardcoded durations and transitions. It also introduces new tests to verify the behavior of the button ring border and scaling animations. The review feedback suggests optimizing performance by deferring state reading of animatedColor and animatedBorderWidth using lambda providers to prevent excessive recompositions of the CaptureButton. Additionally, it is recommended to update the corresponding tests for these lambda parameters and to import CompositionLocalProvider directly in the test file instead of using its fully qualified name.

Comment on lines +617 to +619
captureButtonSize = captureButtonSize,
color = animatedColor
color = animatedColor,
borderWidth = animatedBorderWidth.value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Reading animating states like animatedColor and animatedBorderWidth directly in the body of CaptureButton causes the entire CaptureButton composable to recompose on every single frame of the animations. Pass them as lambda providers to defer state reading and optimize performance.

Note: You will also need to update the CaptureButtonRing definition below, as well as any previews and tests that call it, to accept and pass these lambdas.

Suggested change
captureButtonSize = captureButtonSize,
color = animatedColor
color = animatedColor,
borderWidth = animatedBorderWidth.value
captureButtonSize = captureButtonSize,
color = { animatedColor },
borderWidth = { animatedBorderWidth }
References
  1. Performance and Efficiency: Scan for inefficient operations, especially within Composable functions (e.g., expensive calculations, improper state management leading to excessive recompositions). (link)

Comment on lines +337 to 359
fun captureButtonRing_borderWidthZero_doesNotComposeBorder() {
composeTestRule.setContent {
CaptureButtonRing(
captureButtonSize = 86f,
color = Color.White,
borderWidth = 0f
)
}
composeTestRule.onNodeWithTag(CAPTURE_BUTTON_RING_BORDER).assertDoesNotExist()
}

@Test
fun captureButtonRing_borderWidthGreaterThanZero_composesBorder() {
composeTestRule.setContent {
CaptureButtonRing(
captureButtonSize = 86f,
color = Color.White,
borderWidth = 3f
)
}
composeTestRule.onNodeWithTag(CAPTURE_BUTTON_RING_BORDER).assertExists()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Update the CaptureButtonRing tests to match the new signature that accepts lambda providers for color and borderWidth.

    @Test
    fun captureButtonRing_borderWidthZero_doesNotComposeBorder() {
        composeTestRule.setContent {
            CaptureButtonRing(
                captureButtonSize = 86f,
                color = { Color.White },
                borderWidth = { 0.dp }
            )
        }
        composeTestRule.onNodeWithTag(CAPTURE_BUTTON_RING_BORDER).assertDoesNotExist()
    }

    @Test
    fun captureButtonRing_borderWidthGreaterThanZero_composesBorder() {
        composeTestRule.setContent {
            CaptureButtonRing(
                captureButtonSize = 86f,
                color = { Color.White },
                borderWidth = { 3.dp }
            )
        }
        composeTestRule.onNodeWithTag(CAPTURE_BUTTON_RING_BORDER).assertExists()
    }

Comment on lines +257 to +259
androidx.compose.runtime.CompositionLocalProvider(
LocalDisableAnimations provides true
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Avoid using fully qualified names for common Compose APIs like CompositionLocalProvider. Instead, import androidx.compose.runtime.CompositionLocalProvider at the top of the file and use it directly to improve code readability and maintainability.

Suggested change
androidx.compose.runtime.CompositionLocalProvider(
LocalDisableAnimations provides true
) {
CompositionLocalProvider(
LocalDisableAnimations provides true
) {

Extracts the recording red color (Color(0xFFED0000)) into an internal CaptureTokens object in :ui:components:capture. Updates CaptureButtonNucleus and ElapsedTimeText to use the shared token instead of duplicating the literal color value, and adds a unit test verifying the token value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant