Skip to content

Conversation

@allenchen1154
Copy link
Contributor

@allenchen1154 allenchen1154 commented Sep 6, 2025

This adds support for capturing animated PNGs of components annotated with @ShowkaseComposable, by specifying the new screenshotCaptureType parameter. For example, to capture an animation for 2 seconds with a framerate of 60fps:

@ShowkaseComposable(
    name = "MyAnimatedComponent",
    group = "AnimatedGroup",
    defaultStyle = true,
    screenshotCaptureType = ScreenshotCaptureType.SingleAnimatedImage,
    captureDurationMillis = 2000,
    captureFramerate = 60,
)
@Composable
fun MyAnimatedComponentPreview_Default() {
    var animatedComponentState by remember { mutableStateOf(...) }

    LaunchedEffect(Unit) {
        animatedComponentState = ...
    }

    MyAnimatedComponent(animatedComponentState)
}

Updates Paparazzi to 2.0.0-alpha02 to fix this issue with compileSdk 36.

Screen.Recording.2025-09-05.at.21.19.47.mov

This adds support for capturing animated PNGs of components annotated with `@ShowkaseComposable`, by specifying the new `screenshotConfig` parameter. For example, to capture an animation for 2 seconds with a framerate of 60fps:
```kotlin
@ShowkaseComposable(
    name = "MyAnimatedComponent",
    group = "AnimatedGroup",
    defaultStyle = true,
    screenshotCaptureType = ScreenshotCaptureType.SingleAnimatedImage,
    captureDurationMillis = 2000,
    captureFramerate = 60,
)
@composable
fun MyAnimatedComponentPreview_Default() {
    var animatedComponentState by remember { mutableStateOf(...) }

    LaunchedEffect(Unit) {
        animatedComponentState = ...
    }

    MyAnimatedComponent(animatedComponentState)
}

```

Updates Paparazzi to `2.0.0-alpha02` to fix [this issue](cashapp/paparazzi#1877) with compileSdk 36.
testClassName: String
) {
val showkaseScreenshotTestClassName = "${testClassName}_PaparazziShowkaseTest"
val showkaseScreenshotTestClassName = "${testClassName}Impl"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: This contains the fixes in #419 to shorten the generated Paparazzi image filenames - due to changes in Paparazzi, we can now encounter java.io.FileNotFoundException: File name too long on some systems.

/**
* Configuration for how screenshots of the annotated Composable should be captured.
*/
sealed interface ScreenshotConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice!

* Used by Paparazzi snapshot testing to determine if the component has any animation, and how to capture
* the screenshot.
*/
val screenshotCaptureType: ScreenshotCaptureType = ScreenshotCaptureType.SingleStaticImage,
Copy link
Collaborator

@vinaygaba vinaygaba Sep 8, 2025

Choose a reason for hiding this comment

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

Wish there was a cleaner way (with a single annotation param) to do this so that it was more encapsulated. I think Arrays are the only choice but that's ugly.

Copy link
Contributor Author

@allenchen1154 allenchen1154 Sep 8, 2025

Choose a reason for hiding this comment

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

Yeah unfortunately we're limited by what annotation classes support, hence the need to convert it to ScreenshotConfig later down the line.

Copy link
Contributor

Choose a reason for hiding this comment

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

@allenchen1154 can you use nested annotations to encapsulate all animation related properties?

eg

annotation class Foo(val bar: Bar)

annotation class Bar(val x: Int)

@Foo(bar = Bar(x = 1))
class Baz

Copy link
Collaborator

Choose a reason for hiding this comment

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

What @elihart is suggesting would be really neat!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@elihart thanks for the suggestion, moved these params into a wrapper annotation

@vinaygaba
Copy link
Collaborator

What's the current behavior when it comes to the Showkase browser itself? How are those preview rendered in the browser? Additionally, if you are using offsets, should they show up as separate previews? I think that would be the ideal behavior but since you are using Paparazzi for the screenshots, I suspect that might not be possible without some additional work as the browser shows live composable instead.

@vinaygaba
Copy link
Collaborator

In an ideal case, we maintain parity between what you see in the browser and what gets screenshot tested.

@allenchen1154
Copy link
Contributor Author

@vinaygaba Currently the previews that show in the Showkase browser would just play the animation as it's specified by the preview Composable. So in the example I added, it would simply animate the red square to the right one time. You're right that generating separate browser components for the offsets would require more work/codegen. I think having this discrepancy is OK for now, as I'm hoping the APNG approach will be preferred.

@allenchen1154
Copy link
Contributor Author

@vinaygaba (The offset approach doesn't actually work right now due to a bug in Paparazzi: cashapp/paparazzi#1645)

Copy link
Collaborator

@vinaygaba vinaygaba left a comment

Choose a reason for hiding this comment

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

Changes LGTM. Would be nice to add test coverage - processor tests especially

This test checks that when `screenshotCaptureType` is provided, the generated`ShowkaseBrowserComponent` instances have the correct `ScreenshotConfig` values.
@allenchen1154
Copy link
Contributor Author

@vinaygaba I added a processor test, will merge and prepare a 1.1.0 release.

@allenchen1154 allenchen1154 merged commit b722d73 into master Sep 23, 2025
4 checks passed
allenchen1154 added a commit that referenced this pull request Oct 3, 2025
Includes changes from #421 back-ported to work on older version of XProcessing library, plus necessary changes to use newer Paparazzi
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.

Paparazzi crashing on compileSdk 36 (Android 16 Baklava)

3 participants