Skip to content

feat(preview): support configurable Compose preview annotation - #41

Merged
kingsword09 merged 1 commit into
mainfrom
feat-1
Jun 4, 2026
Merged

kingsword09 merged 1 commit into
mainfrom
feat-1

Conversation

@kingsword09

Copy link
Copy Markdown
Owner
  • add previewAnnotationClass DSL option with AndroidX Preview as default
  • normalize generated preview imports for modern Compose Multiplatform
  • document Preview usage for Compose Multiplatform 1.10+ and legacy versions
  • update example to Kotlin 2.3.21 and Compose Multiplatform 1.11.1
  • switch example official icon source to GitHub raw to avoid rawcdn 403 failures
  • refresh generated example icons

  - add previewAnnotationClass DSL option with AndroidX Preview as default
  - normalize generated preview imports for modern Compose Multiplatform
  - document Preview usage for Compose Multiplatform 1.10+ and legacy versions
  - update example to Kotlin 2.3.21 and Compose Multiplatform 1.11.1
  - switch example official icon source to GitHub raw to avoid rawcdn 403 failures
  - refresh generated example icons

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

Copy link
Copy Markdown

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 introduces a new previewAnnotationClass configuration option to SymbolCraft, allowing users to customize the fully qualified class name of the @Preview annotation used in generated icon preview functions, which now defaults to androidx.compose.ui.tooling.preview.Preview. It updates the converter, Gradle plugin extension, and example app to support this feature, alongside upgrading Kotlin and Compose Multiplatform versions in the example. The code reviewer suggested a performance optimization in Svg2ComposeConverter.kt to replace multiple full-string splits and joins with a single-pass line-by-line processing loop, preventing build overhead on large icon libraries.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +239 to +286
private fun normalizePreviewAnnotation(
content: String,
generatePreview: Boolean,
previewAnnotationClass: String,
): String {
if (!generatePreview || !content.contains("@Preview")) return content

val annotationClass = previewAnnotationClass.trim()
require(annotationClass.matches(KOTLIN_QUALIFIED_NAME_REGEX)) {
"previewAnnotationClass must be a fully qualified Kotlin annotation class name. " +
"Got: '$previewAnnotationClass'"
}

val annotationName = annotationClass.substringAfterLast('.')
val withoutPreviewImports =
content
.lines()
.filterNot { line ->
val importedClass = line.removePrefix("import ").trim()
line.startsWith("import ") &&
(importedClass in GENERATED_PREVIEW_IMPORTS ||
importedClass == annotationClass)
}
.joinToString("\n")

val withAnnotationName =
withoutPreviewImports.replace(PREVIEW_ANNOTATION_REGEX, "@$annotationName")

return addImportIfMissing(withAnnotationName, annotationClass)
}

/** Adds an import after the existing import block, or immediately after the package line. */
private fun addImportIfMissing(content: String, qualifiedName: String): String {
if (content.lines().any { it == "import $qualifiedName" }) return content

val lines = content.lines().toMutableList()
val lastImportIndex = lines.indexOfLast { it.startsWith("import ") }
val packageLineIndex = lines.indexOfFirst { it.startsWith("package ") }
val insertIndex =
when {
lastImportIndex >= 0 -> lastImportIndex + 1
packageLineIndex >= 0 -> packageLineIndex + 1
else -> 0
}

lines.add(insertIndex, "import $qualifiedName")
return lines.joinToString("\n")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation of normalizePreviewAnnotation and addImportIfMissing performs multiple full-string splits (content.lines()) and joins (joinToString("\n")) on every generated file. Since this post-processing runs for every generated icon, this overhead can significantly slow down build times for large icon libraries.

We can optimize this into a single-pass line-by-line processing loop that filters imports, replaces the annotation, and inserts the new import at the correct position in one go.

    private fun normalizePreviewAnnotation(
        content: String,
        generatePreview: Boolean,
        previewAnnotationClass: String,
    ): String {
        if (!generatePreview || !content.contains("@Preview")) return content

        val annotationClass = previewAnnotationClass.trim()
        require(annotationClass.matches(KOTLIN_QUALIFIED_NAME_REGEX)) {
            "previewAnnotationClass must be a fully qualified Kotlin annotation class name. " +
                "Got: '$previewAnnotationClass'"
        }

        val annotationName = annotationClass.substringAfterLast('.')
        val lines = content.lines()
        val filteredLines = ArrayList<String>(lines.size + 1)
        var lastImportIndex = -1
        var packageLineIndex = -1

        for (line in lines) {
            if (line.startsWith("import ")) {
                val importedClass = line.removePrefix("import ").trim()
                if (importedClass in GENERATED_PREVIEW_IMPORTS || importedClass == annotationClass) {
                    continue
                }
                filteredLines.add(line)
                lastImportIndex = filteredLines.size - 1
            } else {
                if (line.startsWith("package ")) {
                    packageLineIndex = filteredLines.size
                }
                val replacedLine = if (line.contains("@Preview")) {
                    line.replace(PREVIEW_ANNOTATION_REGEX, "@$annotationName")
                } else {
                    line
                }
                filteredLines.add(replacedLine)
            }
        }

        val insertIndex = when {
            lastImportIndex >= 0 -> lastImportIndex + 1
            packageLineIndex >= 0 -> packageLineIndex + 1
            else -> 0
        }

        filteredLines.add(insertIndex, "import $annotationClass")
        return filteredLines.joinToString("\\n")
    }

@kingsword09 kingsword09 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixes #40

@kingsword09
kingsword09 merged commit e236be0 into main Jun 4, 2026
14 checks passed
@kingsword09
kingsword09 deleted the feat-1 branch June 4, 2026 01:23
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