diff --git a/Ext/Paparazzi/README.md b/Ext/Paparazzi/README.md
new file mode 100644
index 000000000..5681c1698
--- /dev/null
+++ b/Ext/Paparazzi/README.md
@@ -0,0 +1,133 @@
+# Testify — Android Screenshot Testing — Paparazzi Extensions
+
+
+
+**Utility library for [Paparazzi](https://github.com/cashapp/paparazzi) snapshot testing, providing factory functions, theme helpers, and multi-variant testing support for Compose UIs.**
+
+Paparazzi snapshot tests often repeat identical boilerplate: rule construction, theme wrapping, and manual light/dark duplication. The Testify Paparazzi extension eliminates this repetition by providing:
+
+- **Device presets** — Curated set of common device configurations (phone, tablet, foldable).
+- **Theme helpers** — A `ThemeProvider` interface and extension functions for automatic light/dark snapshot coverage.
+- **Factory functions** — `TestifyPaparazzi.component()` and `TestifyPaparazzi.screen()` replace repetitive `Paparazzi(...)` constructors.
+- **Font scale testing** — Presets and helpers for verifying accessibility font sizes.
+- **Locale/RTL testing** — Presets for internationalization and pseudolocalization testing.
+- **Accessibility snapshots** — Pre-configured `AccessibilityRenderExtension` factory.
+- **State matrix testing** — Snapshot multiple component states from a single test method.
+- **ComposableSnapshotRule** — A high-level JUnit rule combining all features into one declaration.
+
+# Set up testify-paparazzi
+
+**settings.gradle**
+
+Ensure that `mavenCentral()` is available in `dependencyResolutionManagement`.
+
+**Application build.gradle**
+```groovy
+dependencies {
+ testImplementation "dev.testify:testify-paparazzi:5.0.1"
+ testImplementation "app.cash.paparazzi:paparazzi:2.0.0-alpha04"
+}
+```
+
+# Write a test
+
+### Basic snapshot with theme
+
+Define a `ThemeProvider` for your app's theme:
+
+```kotlin
+val myThemeProvider = ThemeProvider { darkTheme, content ->
+ MyAppTheme(darkTheme = darkTheme) { content() }
+}
+```
+
+### Using ComposableSnapshotRule
+
+The highest-level API. A single rule declaration provides themed snapshots with no boilerplate:
+
+```kotlin
+class MyComponentTest {
+
+ @get:Rule val snapshot = ComposableSnapshotRule(themeProvider = myThemeProvider)
+
+ @Test fun default() = snapshot.snapshot { MyComponent() }
+
+ @Test fun darkTheme() = snapshot.snapshot(variant = ThemeVariant.DARK) { MyComponent() }
+
+ @Test fun allThemes() = snapshot.snapshotAllThemes { MyComponent() }
+}
+```
+
+### Using factory functions directly
+
+For more control, use `TestifyPaparazzi` factory functions with the snapshot extension functions:
+
+```kotlin
+class MyComponentTest {
+
+ @get:Rule val paparazzi = TestifyPaparazzi.component()
+
+ @Test fun default() {
+ paparazzi.themedSnapshot(myThemeProvider) { MyComponent() }
+ }
+
+ @Test fun allThemes() {
+ paparazzi.snapshotAllThemes(myThemeProvider) { MyComponent() }
+ }
+}
+```
+
+### State matrix testing
+
+Snapshot multiple component states from a single test method:
+
+```kotlin
+@Test fun ratingStates() {
+ paparazzi.snapshotStates(
+ variants = listOf(
+ StateVariant("zero_stars", 0),
+ StateVariant("three_stars", 3),
+ StateVariant("five_stars", 5),
+ ),
+ themeProvider = myThemeProvider,
+ ) { rating ->
+ RatingBar(rating = rating)
+ }
+}
+```
+
+### Font scale testing
+
+Verify your UI at different accessibility font sizes:
+
+```kotlin
+@Test fun largeFonts() {
+ paparazzi.snapshotAllFontScales { MyComponent() }
+}
+```
+
+---
+
+# License
+
+ MIT License
+
+ Copyright (c) 2026 ndtp
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/Ext/Paparazzi/build.gradle b/Ext/Paparazzi/build.gradle
new file mode 100644
index 000000000..8b03599b2
--- /dev/null
+++ b/Ext/Paparazzi/build.gradle
@@ -0,0 +1,64 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
+
+plugins {
+ id 'java-library'
+ id 'org.jetbrains.kotlin.jvm'
+ alias(libs.plugins.compose.compiler)
+ id 'org.jetbrains.dokka'
+ id 'maven-publish'
+ id 'signing'
+}
+
+ext {
+ pom = [
+ publishedGroupId : 'dev.testify',
+ artifact : 'testify-paparazzi',
+ libraryName : 'testify-paparazzi',
+ libraryDescription: 'Paparazzi snapshot testing utilities for Android Testify',
+ siteUrl : 'https://github.com/ndtp/android-testify',
+ gitUrl : 'https://github.com/ndtp/android-testify.git',
+ licenseName : 'The MIT License',
+ licenseUrl : 'https://opensource.org/licenses/MIT',
+ author : 'ndtp'
+ ]
+}
+
+version = project.findProperty("testify_version") ?: "0.0.1-SNAPSHOT"
+group = pom.publishedGroupId
+archivesBaseName = pom.artifact
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+}
+
+task sourcesJar(type: Jar) {
+ archiveClassifier.set('sources')
+ from sourceSets.main.allSource
+}
+
+task javadocJar(type: Jar, dependsOn: dokkaGenerateModuleHtml) {
+ archiveClassifier.set('javadoc')
+ from dokkaGenerateModuleHtml.outputs
+}
+
+dependencies {
+ compileOnly libs.paparazzi
+ compileOnly libs.junit4
+
+ compileOnly(platform(libs.androidx.compose.bom))
+ compileOnly libs.androidx.compose.runtime
+ compileOnly libs.androidx.ui
+}
+
+tasks.withType(KotlinJvmCompile).configureEach {
+ compilerOptions {
+ allWarningsAsErrors.set(true)
+ jvmTarget.set(JvmTarget.JVM_21)
+ }
+}
+
+afterEvaluate {
+ apply from: "../../publish.build.gradle"
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/AccessibilitySnapshot.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/AccessibilitySnapshot.kt
new file mode 100644
index 000000000..ede20a0ed
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/AccessibilitySnapshot.kt
@@ -0,0 +1,48 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import app.cash.paparazzi.accessibility.AccessibilityRenderExtension
+
+/**
+ * Creates a [Paparazzi] instance with [AccessibilityRenderExtension] pre-configured.
+ *
+ * The accessibility render extension overlays accessibility metadata (content descriptions,
+ * roles, and touch target sizes) on top of the rendered snapshot, making it easy to verify
+ * that composables are properly annotated for screen readers.
+ *
+ * @param device The device configuration to use. Defaults to [TestifyPaparazzi.defaultDevice].
+ * @param theme The Android theme to apply. Defaults to [TestifyPaparazzi.defaultTheme].
+ * @return A [Paparazzi] instance configured with the accessibility render extension.
+ */
+fun TestifyPaparazzi.accessibility(
+ device: DevicePreset = defaultDevice,
+ theme: String = defaultTheme,
+): Paparazzi = Paparazzi(
+ deviceConfig = device.config,
+ theme = theme,
+ renderExtensions = setOf(AccessibilityRenderExtension()),
+)
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ComposableSnapshotRule.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ComposableSnapshotRule.kt
new file mode 100644
index 000000000..92af02b5f
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ComposableSnapshotRule.kt
@@ -0,0 +1,135 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode
+import androidx.compose.runtime.Composable
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A high-level JUnit [TestRule] that wraps [Paparazzi] and bundles an optional [ThemeProvider].
+ *
+ * Combines Paparazzi rule lifecycle management, theme wrapping, and multi-variant snapshot
+ * helpers into a single rule declaration. This is the highest-level API in the library,
+ * reducing a typical test to:
+ *
+ * ```kotlin
+ * @get:Rule val snapshot = ComposableSnapshotRule(themeProvider = myThemeProvider)
+ *
+ * @Test fun myComponent() = snapshot.snapshot { MyComponent() }
+ * ```
+ *
+ * @param device The [DevicePreset] to render on. Defaults to [DevicePreset.PHONE].
+ * @param renderingMode The [RenderingMode] for layout sizing. Defaults to [RenderingMode.SHRINK].
+ * @param theme The Android theme to apply. Defaults to [TestifyPaparazzi.defaultTheme].
+ * @param themeProvider An optional [ThemeProvider] for wrapping content in the app's Compose theme.
+ */
+class ComposableSnapshotRule(
+ device: DevicePreset = DevicePreset.PHONE,
+ renderingMode: RenderingMode = RenderingMode.SHRINK,
+ theme: String = TestifyPaparazzi.defaultTheme,
+ val themeProvider: ThemeProvider? = null,
+) : TestRule {
+
+ private val paparazzi = Paparazzi(
+ deviceConfig = device.config,
+ theme = theme,
+ renderingMode = renderingMode,
+ )
+
+ override fun apply(base: Statement, description: Description): Statement =
+ paparazzi.apply(base, description)
+
+ /**
+ * Takes a snapshot of [content], optionally wrapped in the [themeProvider].
+ *
+ * If a [themeProvider] was supplied at construction, the content is automatically
+ * wrapped in the theme for the given [variant]. Otherwise, the content is rendered as-is.
+ *
+ * @param name An optional name for the snapshot file.
+ * @param variant The [ThemeVariant] to apply. Defaults to [ThemeVariant.LIGHT].
+ * @param content The composable content to snapshot.
+ */
+ fun snapshot(
+ name: String? = null,
+ variant: ThemeVariant = ThemeVariant.LIGHT,
+ content: @Composable () -> Unit,
+ ) {
+ if (themeProvider != null) {
+ paparazzi.themedSnapshot(
+ themeProvider = themeProvider,
+ variant = variant,
+ name = name,
+ content = content,
+ )
+ } else {
+ paparazzi.snapshot(name = name, composable = content)
+ }
+ }
+
+ /**
+ * Takes a snapshot of [content] for every [ThemeVariant] (light and dark).
+ *
+ * Requires a [themeProvider] to have been set at construction time.
+ *
+ * @param name An optional base name prefix for the snapshot files.
+ * @param content The composable content to snapshot.
+ * @throws IllegalArgumentException if [themeProvider] is `null`.
+ */
+ fun snapshotAllThemes(
+ name: String = "",
+ content: @Composable () -> Unit,
+ ) {
+ requireNotNull(themeProvider) { "themeProvider must be set to use snapshotAllThemes" }
+ paparazzi.snapshotAllThemes(
+ themeProvider = themeProvider,
+ name = name,
+ content = content,
+ )
+ }
+
+ /**
+ * Takes a snapshot of [content] for each state in [variants].
+ *
+ * If a [themeProvider] was supplied at construction, each snapshot is wrapped in the theme.
+ *
+ * @param T The type of the state value.
+ * @param variants The list of [StateVariant] values to iterate over.
+ * @param content The composable content to snapshot, parameterized by the state value.
+ */
+ fun snapshotStates(
+ variants: List>,
+ content: @Composable (T) -> Unit,
+ ) {
+ paparazzi.snapshotStates(
+ variants = variants,
+ themeProvider = themeProvider,
+ content = content,
+ )
+ }
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/DevicePreset.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/DevicePreset.kt
new file mode 100644
index 000000000..66e10b3b9
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/DevicePreset.kt
@@ -0,0 +1,60 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.DeviceConfig
+
+/**
+ * Curated set of common device configurations for Paparazzi snapshot tests.
+ *
+ * Wraps Paparazzi's [DeviceConfig] presets into a semantic enum, eliminating the need
+ * to reference specific device model constants directly in test code.
+ *
+ * @property config The underlying Paparazzi [DeviceConfig] for this preset.
+ */
+enum class DevicePreset(val config: DeviceConfig) {
+ /** Standard phone form factor (Pixel 5). */
+ PHONE(DeviceConfig.PIXEL_5),
+
+ /** Smaller phone form factor (Nexus 5). */
+ PHONE_SMALL(DeviceConfig.NEXUS_5),
+
+ /** Tablet form factor (Pixel C). */
+ TABLET(DeviceConfig.PIXEL_C),
+
+ /** Foldable form factor (Pixel Fold). */
+ FOLDABLE(DeviceConfig.PIXEL_FOLD);
+
+ companion object {
+ /** The default device preset used when none is specified. */
+ val DEFAULT = PHONE
+
+ /** All phone-sized presets. */
+ val ALL_PHONES = listOf(PHONE, PHONE_SMALL)
+
+ /** All available device presets. */
+ val ALL = entries
+ }
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/FontScalePreset.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/FontScalePreset.kt
new file mode 100644
index 000000000..9efb2d792
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/FontScalePreset.kt
@@ -0,0 +1,138 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+
+/**
+ * Predefined font scale values for accessibility testing.
+ *
+ * Maps to the font size settings available in Android's accessibility options.
+ * Use with [snapshotWithFontScale] or [snapshotAllFontScales] to verify that UI
+ * components render correctly at larger text sizes.
+ *
+ * @property scale The font scale multiplier (1.0 = default system size).
+ */
+enum class FontScalePreset(val scale: Float) {
+ /** Default system font scale (1.0x). */
+ DEFAULT(1.0f),
+
+ /** Large font scale (1.3x). */
+ LARGE(1.3f),
+
+ /** Extra large font scale (1.5x). */
+ EXTRA_LARGE(1.5f),
+
+ /** Largest font scale (2.0x), matching Android's maximum accessibility setting. */
+ LARGEST(2.0f);
+
+ companion object {
+ /** A representative range of scales for accessibility verification: default, large, and largest. */
+ val ACCESSIBILITY_RANGE = listOf(DEFAULT, LARGE, LARGEST)
+
+ /** All available font scale presets. */
+ val ALL = entries
+ }
+}
+
+/**
+ * Takes a snapshot of [content] rendered at the given [fontScale].
+ *
+ * Overrides the [LocalDensity] composition local to apply the specified font scale,
+ * simulating the user's accessibility font size setting.
+ *
+ * @param fontScale The [FontScalePreset] to apply.
+ * @param themeProvider An optional [ThemeProvider] to wrap the content in a theme.
+ * @param variant The [ThemeVariant] to apply if a [themeProvider] is given. Defaults to [ThemeVariant.LIGHT].
+ * @param name An optional name for the snapshot file. Defaults to `"fontScale_{preset}"`.
+ * @param content The composable content to snapshot.
+ */
+fun Paparazzi.snapshotWithFontScale(
+ fontScale: FontScalePreset,
+ themeProvider: ThemeProvider? = null,
+ variant: ThemeVariant = ThemeVariant.LIGHT,
+ name: String? = null,
+ content: @Composable () -> Unit,
+) {
+ val snapshotName = name ?: "fontScale_${fontScale.name.lowercase()}"
+ snapshot(name = snapshotName) {
+ val wrappedContent: @Composable () -> Unit = {
+ val currentDensity = LocalDensity.current
+ CompositionLocalProvider(
+ LocalDensity provides Density(
+ density = currentDensity.density,
+ fontScale = fontScale.scale,
+ ),
+ ) {
+ content()
+ }
+ }
+ if (themeProvider != null) {
+ themeProvider.Provide(darkTheme = variant == ThemeVariant.DARK) {
+ wrappedContent()
+ }
+ } else {
+ wrappedContent()
+ }
+ }
+}
+
+/**
+ * Takes a snapshot of [content] at each of the given font [scales].
+ *
+ * Each snapshot is automatically named with a font scale suffix (e.g. `"fontScale_large"`),
+ * enabling accessibility font-size coverage from a single call.
+ *
+ * @param scales The list of [FontScalePreset] values to iterate over. Defaults to [FontScalePreset.ACCESSIBILITY_RANGE].
+ * @param themeProvider An optional [ThemeProvider] to wrap the content in a theme.
+ * @param variant The [ThemeVariant] to apply if a [themeProvider] is given. Defaults to [ThemeVariant.LIGHT].
+ * @param name An optional base name prefix for the snapshot files.
+ * @param content The composable content to snapshot.
+ */
+fun Paparazzi.snapshotAllFontScales(
+ scales: List = FontScalePreset.ACCESSIBILITY_RANGE,
+ themeProvider: ThemeProvider? = null,
+ variant: ThemeVariant = ThemeVariant.LIGHT,
+ name: String = "",
+ content: @Composable () -> Unit,
+) {
+ scales.forEach { fontScale ->
+ val snapshotName = buildString {
+ if (name.isNotEmpty()) append(name).append("_")
+ append("fontScale_${fontScale.name.lowercase()}")
+ }
+ snapshotWithFontScale(
+ fontScale = fontScale,
+ themeProvider = themeProvider,
+ variant = variant,
+ name = snapshotName,
+ content = content,
+ )
+ }
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/LocalePreset.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/LocalePreset.kt
new file mode 100644
index 000000000..b4fc080bf
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/LocalePreset.kt
@@ -0,0 +1,78 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.DeviceConfig
+
+/**
+ * Predefined locale configurations for internationalization snapshot testing.
+ *
+ * Each preset provides a locale qualifier string compatible with Paparazzi's [DeviceConfig]
+ * and an [isRtl] flag indicating right-to-left layout direction. Includes pseudolocalization
+ * presets for detecting i18n issues such as string truncation and hardcoded text.
+ *
+ * @property qualifiers The locale qualifier string (e.g. `"en"`, `"ar"`) for Paparazzi's [DeviceConfig].
+ * @property isRtl `true` if this locale uses right-to-left layout direction.
+ */
+@Suppress("unused")
+enum class LocalePreset(val qualifiers: String, val isRtl: Boolean = false) {
+ /** English (LTR). */
+ ENGLISH("en"),
+
+ /** English pseudolocale for detecting string truncation and hardcoded text. */
+ ENGLISH_PSEUDO("en-rXA"),
+
+ /** Bidi pseudolocale for detecting RTL layout issues. */
+ BIDI_PSEUDO("ar-rXB"),
+
+ /** Arabic (RTL). */
+ ARABIC("ar", isRtl = true),
+
+ /** German (LTR) — useful for testing long string expansion. */
+ GERMAN("de"),
+
+ /** Japanese (LTR) — useful for testing CJK character rendering. */
+ JAPANESE("ja");
+
+ companion object {
+ /** Both pseudolocalization presets for i18n smoke testing. */
+ val PSEUDOLOCALES = listOf(ENGLISH_PSEUDO, BIDI_PSEUDO)
+
+ /** All right-to-left locale presets. */
+ val RTL_SET = entries.filter { it.isRtl }
+
+ /** A representative set covering LTR, RTL, and CJK scripts. */
+ val CORE_SET = listOf(ENGLISH, ARABIC, JAPANESE)
+ }
+}
+
+/**
+ * Returns a copy of this preset's [DeviceConfig] with the given [locale] applied.
+ *
+ * @param locale The [LocalePreset] to apply to the device configuration.
+ * @return A new [DeviceConfig] with the locale qualifier set.
+ */
+fun DevicePreset.withLocale(locale: LocalePreset): DeviceConfig =
+ config.copy(locale = locale.qualifiers)
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/SnapshotExtensions.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/SnapshotExtensions.kt
new file mode 100644
index 000000000..71ae5efa0
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/SnapshotExtensions.kt
@@ -0,0 +1,82 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import androidx.compose.runtime.Composable
+
+/**
+ * Takes a snapshot of [content] wrapped in the given [themeProvider].
+ *
+ * This is a convenience extension that automatically applies the app's theme around
+ * the composable content before capturing.
+ *
+ * @param themeProvider The [ThemeProvider] used to wrap the content in a theme.
+ * @param variant The [ThemeVariant] to apply (light or dark). Defaults to [ThemeVariant.LIGHT].
+ * @param name An optional name for the snapshot file. If `null`, Paparazzi uses the test method name.
+ * @param content The composable content to snapshot.
+ */
+fun Paparazzi.themedSnapshot(
+ themeProvider: ThemeProvider,
+ variant: ThemeVariant = ThemeVariant.LIGHT,
+ name: String? = null,
+ content: @Composable () -> Unit,
+) {
+ snapshot(name = name) {
+ themeProvider.Provide(darkTheme = variant == ThemeVariant.DARK) {
+ content()
+ }
+ }
+}
+
+/**
+ * Takes a snapshot of [content] for every [ThemeVariant] (light and dark).
+ *
+ * Each snapshot is automatically suffixed with the variant name (e.g. `"_light"`, `"_dark"`),
+ * eliminating the need to manually duplicate test methods for theme coverage.
+ *
+ * @param themeProvider The [ThemeProvider] used to wrap the content in a theme.
+ * @param name An optional base name prefix for the snapshot files. If empty, only the variant
+ * suffix is used.
+ * @param content The composable content to snapshot.
+ */
+fun Paparazzi.snapshotAllThemes(
+ themeProvider: ThemeProvider,
+ name: String = "",
+ content: @Composable () -> Unit,
+) {
+ ThemeVariant.entries.forEach { variant ->
+ val snapshotName = buildString {
+ if (name.isNotEmpty()) append(name).append("_")
+ append(variant.name.lowercase())
+ }
+ themedSnapshot(
+ themeProvider = themeProvider,
+ variant = variant,
+ name = snapshotName,
+ content = content,
+ )
+ }
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/StateVariant.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/StateVariant.kt
new file mode 100644
index 000000000..c90a983f2
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/StateVariant.kt
@@ -0,0 +1,83 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import androidx.compose.runtime.Composable
+
+/**
+ * A named state value for use with [snapshotStates].
+ *
+ * Pairs a human-readable [name] (used as the snapshot file suffix) with a [state] value
+ * that is passed to the composable under test. This replaces the pattern of writing
+ * near-identical test methods that differ only in their input state.
+ *
+ * Example:
+ * ```kotlin
+ * val ratingVariants = listOf(
+ * StateVariant("zero_stars", 0),
+ * StateVariant("three_stars", 3),
+ * StateVariant("five_stars", 5),
+ * )
+ * ```
+ *
+ * @param T The type of the state value.
+ * @property name The name used to identify this variant in snapshot file names.
+ * @property state The state value passed to the composable content.
+ */
+data class StateVariant(val name: String, val state: T)
+
+/**
+ * Takes a snapshot of [content] for each state in [variants].
+ *
+ * Iterates over the provided [StateVariant] list, rendering and capturing a snapshot for
+ * each one. Each snapshot is named using the variant's [StateVariant.name]. Optionally
+ * wraps the content in a theme via [themeProvider].
+ *
+ * @param T The type of the state value.
+ * @param variants The list of [StateVariant] values to iterate over.
+ * @param themeProvider An optional [ThemeProvider] to wrap the content in a theme.
+ * @param variant The [ThemeVariant] to apply if a [themeProvider] is given. Defaults to [ThemeVariant.LIGHT].
+ * @param content The composable content to snapshot, parameterized by the state value.
+ */
+fun Paparazzi.snapshotStates(
+ variants: List>,
+ themeProvider: ThemeProvider? = null,
+ variant: ThemeVariant = ThemeVariant.LIGHT,
+ content: @Composable (T) -> Unit,
+) {
+ variants.forEach { stateVariant ->
+ snapshot(name = stateVariant.name) {
+ val composable: @Composable () -> Unit = { content(stateVariant.state) }
+ if (themeProvider != null) {
+ themeProvider.Provide(darkTheme = variant == ThemeVariant.DARK) {
+ composable()
+ }
+ } else {
+ composable()
+ }
+ }
+ }
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/TestifyPaparazzi.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/TestifyPaparazzi.kt
new file mode 100644
index 000000000..4d2f9e279
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/TestifyPaparazzi.kt
@@ -0,0 +1,87 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import app.cash.paparazzi.Paparazzi
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode
+
+/**
+ * Factory object for creating pre-configured [Paparazzi] instances with sensible defaults.
+ *
+ * Replaces the repetitive `Paparazzi(...)` constructor boilerplate commonly duplicated
+ * across test files. Provides two primary factory methods: [component] for individual
+ * composable components and [screen] for full-screen layouts.
+ *
+ * The [defaultTheme] and [defaultDevice] properties can be configured globally to set
+ * project-wide defaults.
+ */
+object TestifyPaparazzi {
+ /** The default Android theme applied to all Paparazzi instances. */
+ var defaultTheme: String = "android:Theme.Material.Light.NoActionBar"
+
+ /** The default device configuration applied to all Paparazzi instances. */
+ var defaultDevice: DevicePreset = DevicePreset.PHONE
+
+ /**
+ * Creates a [Paparazzi] instance configured for rendering individual components.
+ *
+ * Uses [RenderingMode.SHRINK] by default, which shrinks the rendered output to fit
+ * the composable's measured size.
+ *
+ * @param device The device configuration to use.
+ * @param theme The Android theme to apply.
+ * @param renderingMode The rendering mode for layout sizing.
+ * @return A configured [Paparazzi] instance.
+ */
+ fun component(
+ device: DevicePreset = defaultDevice,
+ theme: String = defaultTheme,
+ renderingMode: RenderingMode = RenderingMode.SHRINK,
+ ): Paparazzi = Paparazzi(
+ deviceConfig = device.config,
+ theme = theme,
+ renderingMode = renderingMode,
+ )
+
+ /**
+ * Creates a [Paparazzi] instance configured for rendering full-screen layouts.
+ *
+ * Uses [RenderingMode.NORMAL] by default, which renders at the device's full screen size.
+ *
+ * @param device The device configuration to use.
+ * @param theme The Android theme to apply.
+ * @param renderingMode The rendering mode for layout sizing.
+ * @return A configured [Paparazzi] instance.
+ */
+ fun screen(
+ device: DevicePreset = defaultDevice,
+ theme: String = defaultTheme,
+ renderingMode: RenderingMode = RenderingMode.NORMAL,
+ ): Paparazzi = Paparazzi(
+ deviceConfig = device.config,
+ theme = theme,
+ renderingMode = renderingMode,
+ )
+}
diff --git a/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ThemeVariant.kt b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ThemeVariant.kt
new file mode 100644
index 000000000..897ba90be
--- /dev/null
+++ b/Ext/Paparazzi/src/main/kotlin/dev/testify/paparazzi/ThemeVariant.kt
@@ -0,0 +1,63 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 ndtp
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package dev.testify.paparazzi
+
+import androidx.compose.runtime.Composable
+
+/**
+ * Represents a light or dark theme variant for snapshot testing.
+ */
+enum class ThemeVariant {
+ /** Light theme variant. */
+ LIGHT,
+
+ /** Dark theme variant. */
+ DARK
+}
+
+/**
+ * Functional interface for providing a Compose theme wrapper around snapshot content.
+ *
+ * Consumers implement this once per project to wrap their app's theme (Material 2, Material 3,
+ * or custom) around composable content during snapshot tests.
+ *
+ * Example:
+ * ```kotlin
+ * val myThemeProvider = ThemeProvider { darkTheme, content ->
+ * MyAppTheme(darkTheme = darkTheme) { content() }
+ * }
+ * ```
+ */
+fun interface ThemeProvider {
+ /**
+ * Wraps [content] in the app's theme.
+ *
+ * @param darkTheme `true` to apply the dark theme variant, `false` for light.
+ * @param content The composable content to render inside the theme.
+ */
+ @Composable
+ @Suppress("ktlint:standard:function-naming")
+ fun Provide(darkTheme: Boolean, content: @Composable () -> Unit)
+}
diff --git a/bitrise.yml b/bitrise.yml
index 7181f4003..9468b078c 100644
--- a/bitrise.yml
+++ b/bitrise.yml
@@ -24,6 +24,7 @@ stages:
- test_accessibility_ext: {}
- test_compose_ext: {}
- test_fullscreen_ext: {}
+ - test_paparazzi_ext: {}
workflows:
evaluate_build:
@@ -84,6 +85,13 @@ workflows:
- auth_token: $GITHUB_TOKEN
- status_identifier: "Fullscreen Extension"
- pipeline_build_url: "$BITRISE_BUILD_URL"
+ - github-status@3:
+ run_if: .IsCI
+ inputs:
+ - set_specific_status: "pending"
+ - auth_token: $GITHUB_TOKEN
+ - status_identifier: "Paparazzi Extension"
+ - pipeline_build_url: "$BITRISE_BUILD_URL"
meta:
bitrise.io:
stack: ubuntu-noble-24.04-bitrise-2025-android
@@ -312,6 +320,31 @@ workflows:
stack: ubuntu-noble-24.04-bitrise-2025-android
machine_type_id: standard
+ test_paparazzi_ext:
+ steps:
+ - gradle-runner@2:
+ inputs:
+ - gradlew_path: "./gradlew"
+ - gradle_task: PaparazziExt:ktlintCheck
+ title: KtLint
+ - gradle-runner@2:
+ inputs:
+ - gradlew_path: "./gradlew"
+ - gradle_task: PaparazziExt:assemble
+ title: Validate build
+ - github-status@3:
+ run_if: .IsCI
+ inputs:
+ - auth_token: $GITHUB_TOKEN
+ - status_identifier: "Paparazzi Extension"
+ - pipeline_build_url: "$BITRISE_BUILD_URL"
+ before_run:
+ - _globalSetup
+ meta:
+ bitrise.io:
+ stack: ubuntu-noble-24.04-bitrise-2025-android
+ machine_type_id: standard
+
test_flix:
summary: Run your Android unit tests and get the test report.
description: The workflow will first clone your Git repository, cache your Gradle
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b40c7e2a3..bf21e8b1e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -34,6 +34,8 @@ truth = "1.4.4"
uiTestJunit4 = "1.9.0"
uiautomator = "2.3.0"
composeBom = "2025.08.01"
+junit4 = "4.13.2"
+paparazzi = "2.0.0-alpha04"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
@@ -55,6 +57,7 @@ androidx-ui = { module = "androidx.compose.ui:ui", version.ref = "uiTestJunit4"
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiTestJunit4" }
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "uiTestJunit4" }
androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" }
+androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
colormath = { module = "com.github.ajalt:colormath", version.ref = "colormath" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtxVersion" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
@@ -65,6 +68,8 @@ material = { module = "com.google.android.material:material", version.ref = "mat
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" }
slf4j-jdk14 = { module = "org.slf4j:slf4j-jdk14", version.ref = "slf4jJdk14" }
+junit4 = { module = "junit:junit", version.ref = "junit4" }
+paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
[plugins]
diff --git a/settings.gradle b/settings.gradle
index 070a66306..82dac2629 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,6 +21,7 @@ include ':LegacySample'
include ':FlixSample'
include ':FlixLibrary'
include ':GmdSample'
+include ':PaparazziExt'
includeBuild("./Plugins/Gradle") { name = "Plugin" }
include ':Library'
include ':ComposeExtensions'
@@ -34,3 +35,4 @@ project(':LegacySample').projectDir = new File("./Samples/Legacy")
project(':FlixSample').projectDir = new File("./Samples/Flix")
project(':FlixLibrary').projectDir = new File("./Samples/Flix/FlixLibrary")
project(':GmdSample').projectDir = new File("./Samples/Gmd")
+project(':PaparazziExt').projectDir = new File("./Ext/Paparazzi")