Skip to content

Commit

Permalink
Support fetching figma file version (#1077)
Browse files Browse the repository at this point in the history
  • Loading branch information
yiqunw700 authored May 15, 2024
1 parent 6c1031d commit b285959
Show file tree
Hide file tree
Showing 33 changed files with 348 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ package com.android.designcompose.annotation
* Generate an interface that contains functions to render various nodes in a Figma document
*
* @param id the id of the Figma document. This can be found in the url, e.g. figma.com/file/<id>
* @param version a version string that gets written to a generated JSON file used for the Design
* Compose Figma plugin
* @param designVersion version id of the Figma document.
* @param customizationInterfaceVersion a version string that gets written to a generated JSON file
* describing the customization interface which is used for the Design Compose Figma plugin.
*/
@Target(AnnotationTarget.CLASS)
annotation class DesignDoc(val id: String, val version: String = "0")
annotation class DesignDoc(
val id: String,
val designVersion: String = "",
val customizationInterfaceVersion: String = "0"
)

/**
* Generate a @Composable function that renders the given node
Expand Down
3 changes: 2 additions & 1 deletion build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ spotless {
}
kotlinGradle { ktfmt(libs.versions.ktfmt.get()).kotlinlangStyle() }
}
kotlin { jvmToolchain(libs.versions.jvmToolchain.get().toInt())}

kotlin { jvmToolchain(libs.versions.jvmToolchain.get().toInt()) }

dependencies {
implementation(libs.android.gradlePlugin)
Expand Down
2 changes: 1 addition & 1 deletion build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ dependencyResolutionManagement {
}
versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
}

plugins {
// Downloads the required Java Toolchain, if needed.
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}

Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
) : KSVisitorVoid() {
private var docName: String = ""
private var docId: String = ""
private var designVersion: String = ""
private var currentFunc = ""
private var textCustomizations: HashMap<String, Vector<Pair<String, String>>> = HashMap()
private var textFunctionCustomizations: HashMap<String, Vector<Pair<String, String>>> =
Expand Down Expand Up @@ -209,6 +210,10 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
docName = className + "Doc"
docId = idArg.value as String

val designVersionArg: KSValueArgument =
annotation.arguments.first { arg -> arg.name?.asString() == "designVersion" }
designVersion = designVersionArg.value as String

// Declare a global document ID that can be changed by the Design Switcher
val docIdVarName = className + "GenId"
out.appendText("private var $docIdVarName: String = \"$docId\"\n\n")
Expand Down Expand Up @@ -251,7 +256,7 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
out.appendText(" @Composable\n")
out.appendText(" final fun DesignSwitcher(modifier: Modifier = Modifier) {\n")
out.appendText(
" val (docId, setDocId) = remember { mutableStateOf(\"$docId\") }\n"
" val (docId, setDocId) = remember { mutableStateOf(DesignDocId(\"$docId\", \"$designVersion\")) }\n"
)
out.appendText(" DesignDoc(\"$docName\", docId, NodeQuery.NodeName(\"\"),\n")
out.appendText(" modifier = modifier,\n")
Expand Down Expand Up @@ -312,7 +317,7 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
out.appendText(" customizations.setTapCallback(nodeName, tapCallback)\n")
out.appendText(" customizations.mergeFrom(LocalCustomizationContext.current)\n")
out.appendText(
" val (docId, setDocId) = remember { mutableStateOf(\"$docId\") }\n"
" val (docId, setDocId) = remember { mutableStateOf(DesignDocId(\"$docId\", \"$designVersion\")) }\n"
)
out.appendText(" val queries = queries()\n")
out.appendText(" queries.add(nodeName)\n")
Expand Down Expand Up @@ -342,10 +347,13 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
// Write the design doc JSON for our plugin
designDocJson.addProperty("name", className)
designDocJson.add("components", jsonComponents)
val versionArg = annotation.arguments.find { arg -> arg.name?.asString() == "version" }
if (versionArg != null) {
val versionString = versionArg.value as String
designDocJson.addProperty("version", versionString)
val customizationInterfaceVersionArg =
annotation.arguments.find { arg ->
arg.name?.asString() == "customizationInterfaceVersion"
}
if (customizationInterfaceVersionArg != null) {
val customizationInterfaceVersion = customizationInterfaceVersionArg.value as String
designDocJson.addProperty("version", customizationInterfaceVersion)
}

val gson = GsonBuilder().setPrettyPrinting().create()
Expand Down Expand Up @@ -760,7 +768,7 @@ class BuilderProcessor(private val codeGenerator: CodeGenerator, val logger: KSP
// Create a mutable state so that the Design Switcher can dynamically change the
// document ID
out.appendText(
" val (docId, setDocId) = remember { mutableStateOf(\"$docId\") }\n"
" val (docId, setDocId) = remember { mutableStateOf(DesignDocId(\"$docId\", \"$designVersion\")) }\n"
)

// If there are variants, add the variant name to the list of queries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ internal fun createNewFile(

file += "import com.android.designcompose.annotation.DesignMetaKey\n"
file += "import com.android.designcompose.serdegen.NodeQuery\n"
file += "import com.android.designcompose.common.DesignDocId\n"
file += "import com.android.designcompose.common.DocumentServerParams\n"
file += "import com.android.designcompose.ComponentReplacementContext\n"
file += "import com.android.designcompose.ImageReplacementContext\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.designcompose.common

/**
* Id for the figma design doc with file id and version id. When version id is not specified, it
* will load head of the figma doc.
*/
data class DesignDocId(var id: String, var versionId: String = "") {
fun isValid(): Boolean {
return id.isNotEmpty()
}

override fun toString(): String {
return if (versionId.isEmpty()) id else "${id}_$versionId"
}
}
51 changes: 30 additions & 21 deletions common/src/main/java/com/android/designcompose/common/Feedback.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class FeedbackMessage(
// Basic implementation of the Feedback class, used by docloader and Design Compose
abstract class FeedbackImpl {
private val messages: ArrayDeque<FeedbackMessage> = ArrayDeque()
private val ignoredDocuments: HashSet<String> = HashSet()
private val ignoredDocuments: HashSet<DesignDocId> = HashSet()
private var logLevel: FeedbackLevel = FeedbackLevel.Info
private var maxMessages = 20
var messagesListId = 0 // Change this every time the list changes so we can update subscribers
Expand All @@ -52,12 +52,12 @@ abstract class FeedbackImpl {
maxMessages = num
}

fun addIgnoredDocument(docId: String): Boolean {
fun addIgnoredDocument(docId: DesignDocId): Boolean {
ignoredDocuments.add(docId)
return true
}

fun isDocumentIgnored(docId: String): Boolean {
fun isDocumentIgnored(docId: DesignDocId): Boolean {
return ignoredDocuments.contains(docId)
}

Expand All @@ -70,20 +70,20 @@ abstract class FeedbackImpl {
// fun newDocServer(url: String){
// }

fun diskLoadFail(id: String, docId: String) {
fun diskLoadFail(id: String, docId: DesignDocId) {
setStatus(
"Unable to open $id from disk; will try live and from assets",
FeedbackLevel.Debug,
docId
)
}

fun documentUnchanged(docId: String) {
fun documentUnchanged(docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Live update for $truncatedId unchanged...", FeedbackLevel.Info, docId)
}

fun documentUpdated(docId: String, numSubscribers: Int) {
fun documentUpdated(docId: DesignDocId, numSubscribers: Int) {
val truncatedId = shortDocId(docId)
setStatus(
"Live update for $truncatedId fetched and informed $numSubscribers subscribers",
Expand All @@ -92,7 +92,7 @@ abstract class FeedbackImpl {
)
}

fun documentUpdateCode(docId: String, code: Int) {
fun documentUpdateCode(docId: DesignDocId, code: Int) {
val truncatedId = shortDocId(docId)
setStatus(
"Live update for $truncatedId unexpected server response: $code",
Expand All @@ -101,17 +101,17 @@ abstract class FeedbackImpl {
)
}

fun documentUpdateWarnings(docId: String, msg: String) {
fun documentUpdateWarnings(docId: DesignDocId, msg: String) {
val truncatedId = shortDocId(docId)
setStatus("Live update for $truncatedId warning: $msg", FeedbackLevel.Warn, docId)
}

fun documentUpdateError(docId: String, msg: String) {
fun documentUpdateError(docId: DesignDocId, msg: String) {
val truncatedId = shortDocId(docId)
setStatus("Live update for $truncatedId failed: $msg", FeedbackLevel.Error, docId)
}

fun documentUpdateErrorRevert(docId: String, msg: String) {
fun documentUpdateErrorRevert(docId: DesignDocId, msg: String) {
val truncatedId = shortDocId(docId)
setStatus(
"Live update for $truncatedId failed: $msg, reverting to original doc ID",
Expand All @@ -120,22 +120,22 @@ abstract class FeedbackImpl {
)
}

fun documentDecodeStart(docId: String) {
fun documentDecodeStart(docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Starting to read doc $truncatedId...", FeedbackLevel.Debug, docId)
}

fun documentDecodeReadBytes(size: Int, docId: String) {
fun documentDecodeReadBytes(size: Int, docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Read $size bytes of doc $truncatedId", FeedbackLevel.Info, docId)
}

fun documentDecodeError(docId: String) {
fun documentDecodeError(docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Error decoding doc $truncatedId", FeedbackLevel.Warn, docId)
}

fun documentDecodeVersionMismatch(expected: Int, actual: Int, docId: String) {
fun documentDecodeVersionMismatch(expected: Int, actual: Int, docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus(
"Wrong version in doc $truncatedId: Expected $expected but found $actual",
Expand All @@ -144,30 +144,35 @@ abstract class FeedbackImpl {
)
}

fun documentDecodeSuccess(version: Int, name: String, lastModified: String, docId: String) {
fun documentDecodeSuccess(
version: Int,
name: String,
lastModified: String,
docId: DesignDocId
) {
setStatus(
"Successfully deserialized V$version doc. Name: $name, last modified: $lastModified",
FeedbackLevel.Info,
docId
)
}

fun documentSaveTo(path: String, docId: String) {
fun documentSaveTo(path: String, docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Saving doc $truncatedId to $path", FeedbackLevel.Info, docId)
}

fun documentSaveSuccess(docId: String) {
fun documentSaveSuccess(docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Save doc $truncatedId success", FeedbackLevel.Info, docId)
}

fun documentSaveError(error: String, docId: String) {
fun documentSaveError(error: String, docId: DesignDocId) {
val truncatedId = shortDocId(docId)
setStatus("Unable to save doc $truncatedId: $error", FeedbackLevel.Error, docId)
}

open fun setStatus(str: String, level: FeedbackLevel, docId: String) {
open fun setStatus(str: String, level: FeedbackLevel, docId: DesignDocId) {
// Ignore log levels we don't care about
if (level < logLevel) return

Expand All @@ -189,8 +194,12 @@ abstract class FeedbackImpl {
++messagesListId
}

protected fun shortDocId(docId: String): String {
return if (docId.length > 7) docId.substring(0, 7) else docId
protected fun shortDocId(docId: DesignDocId): String {
val id = if (docId.id.length > 7) docId.id.substring(0, 7) else docId.id
val versionId =
if (docId.versionId.length > 4) docId.versionId.substring(docId.versionId.length - 4)
else docId.versionId
return if (versionId.isEmpty()) id else "${id}/${versionId}"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import java.io.FileOutputStream
import java.io.InputStream

class GenericDocContent(
var docId: String,
var docId: DesignDocId,
private val header: SerializedDesignDocHeader,
val document: SerializedDesignDoc,
val variantViewMap: HashMap<String, HashMap<String, View>>,
Expand Down Expand Up @@ -70,7 +70,7 @@ class GenericDocContent(
/// Read a serialized server document from the given stream. Deserialize it and save it to disk.
fun decodeServerBaseDoc(
docBytes: ByteArray,
docId: String,
docId: DesignDocId,
feedback: FeedbackImpl
): GenericDocContent? {
val deserializer = BincodeDeserializer(docBytes)
Expand Down Expand Up @@ -99,7 +99,11 @@ fun decodeServerBaseDoc(
}

/// Read a serialized disk document from the given stream. Deserialize it and deserialize its images
fun decodeDiskBaseDoc(doc: InputStream, docId: String, feedback: FeedbackImpl): GenericDocContent? {
fun decodeDiskBaseDoc(
doc: InputStream,
docId: DesignDocId,
feedback: FeedbackImpl
): GenericDocContent? {
val docBytes = readDocBytes(doc, docId, feedback)
val deserializer = BincodeDeserializer(docBytes)

Expand Down Expand Up @@ -166,7 +170,7 @@ private fun createVariantPropertyMap(nodes: Map<NodeQuery, View>?): VariantPrope
return propertyMap
}

fun readDocBytes(doc: InputStream, docId: String, feedback: FeedbackImpl): ByteArray {
fun readDocBytes(doc: InputStream, docId: DesignDocId, feedback: FeedbackImpl): ByteArray {
// Read the doc from assets or network...
feedback.documentDecodeStart(docId)
val buffer = ByteArrayOutputStream()
Expand Down Expand Up @@ -201,7 +205,7 @@ fun readErrorBytes(errorStream: InputStream?): String {

private fun decodeHeader(
deserializer: BincodeDeserializer,
docId: String,
docId: DesignDocId,
feedback: FeedbackImpl
): SerializedDesignDocHeader? {
// Now attempt to deserialize the doc)
Expand Down
Loading

0 comments on commit b285959

Please sign in to comment.