From a737219fb524898376a6373ab1a573dba07d6186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adriel=20Caf=C3=A9?= Date: Thu, 14 Apr 2022 21:56:34 -0300 Subject: [PATCH] feat: json integration --- README.md | 31 +++++- .../kotlin/cafe/adriel/bonsai/core/Bonsai.kt | 5 +- bonsai-file-system/build.gradle.kts | 3 +- .../bonsai/filesystem/FileSystemNode.kt | 2 + bonsai-json/.gitignore | 1 + bonsai-json/build.gradle.kts | 22 +++++ bonsai-json/consumer-rules.pro | 0 bonsai-json/gradle.properties | 2 + .../src/androidMain/AndroidManifest.xml | 2 + .../cafe/adriel/bonsai/json/JsonNode.kt | 95 +++++++++++++++++++ gradle.properties | 2 +- gradle/libs.versions.toml | 5 +- sample/build.gradle | 2 +- settings.gradle.kts | 1 + 14 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 bonsai-json/.gitignore create mode 100644 bonsai-json/build.gradle.kts create mode 100644 bonsai-json/consumer-rules.pro create mode 100644 bonsai-json/gradle.properties create mode 100644 bonsai-json/src/androidMain/AndroidManifest.xml create mode 100644 bonsai-json/src/commonMain/kotlin/cafe/adriel/bonsai/json/JsonNode.kt diff --git a/README.md b/README.md index a502190..4509813 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ - [x] Multiplatform: Android, Desktop - [x] State-aware: changes in the tree will trigger recomposition - [x] Unlimited levels -- [x] [File system integration](#file-system) +- [x] [File System integration](#file-system-integration) +- [x] [JSON integration](#json-integration) - [x] [Built-in DSL](#dsl) - [x] [Expandable](#expanding--collapsing) - [x] [Selectable](#selecting) @@ -65,10 +66,9 @@ Output: **Take a look at the [sample app](https://github.com/adrielcafe/bonsai/blob/main/sample/src/main/java/cafe/adriel/bonsai/sample/SampleActivity.kt) for a working example.** -### File System -Bonsai is integrated with file system, you should import `bonsai-file-system` module to use it. +### File System integration +Import `cafe.adriel.bonsai:bonsai-file-system` module to use it. -Instead of manually create the nodes, call `fileSystemNodes()` to generate for you based on a root path. ```kotlin val tree = rememberTree( nodes = fileSystemNodes( @@ -81,7 +81,7 @@ val tree = rememberTree( Bonsai( tree = tree, - // Custom style to show file and directory icons + // Custom style style = FileSystemBonsaiStyle() ) ``` @@ -90,6 +90,26 @@ Output: +### JSON integration +Import `cafe.adriel.bonsai:bonsai-json` module to use it. + +```kotlin +val tree = rememberTree( + // Sample JSON from https://rickandmortyapi.com/api/character + nodes = jsonNodes(json) +) + +Bonsai( + tree = tree, + // Custom style + style = JsonBonsaiStyle() +) +``` + +Output: + + + ### DSL Looking for a simpler and less verbose way to create a tree? Here's a handy DSL for you. ```kotlin @@ -210,6 +230,7 @@ Add the desired dependencies to your module's `build.gradle`: ```gradle implementation "cafe.adriel.bonsai:bonsai-core:${latest-version}" implementation "cafe.adriel.bonsai:bonsai-file-system:${latest-version}" +implementation "cafe.adriel.bonsai:bonsai-json:${latest-version}" ``` Current version: ![Maven metadata URL](https://img.shields.io/maven-metadata/v?color=blue&metadataUrl=https://s01.oss.sonatype.org/service/local/repo_groups/public/content/cafe/adriel/bonsai/bonsai-core/maven-metadata.xml) diff --git a/bonsai-core/src/commonMain/kotlin/cafe/adriel/bonsai/core/Bonsai.kt b/bonsai-core/src/commonMain/kotlin/cafe/adriel/bonsai/core/Bonsai.kt index b0f6fc8..ce5af23 100644 --- a/bonsai-core/src/commonMain/kotlin/cafe/adriel/bonsai/core/Bonsai.kt +++ b/bonsai-core/src/commonMain/kotlin/cafe/adriel/bonsai/core/Bonsai.kt @@ -61,15 +61,14 @@ public data class BonsaiStyle( public val nodeCollapsedColorFilter: ColorFilter? = null, public val nodeExpandedIcon: NodeIcon = nodeCollapsedIcon, public val nodeExpandedColorFilter: ColorFilter? = nodeCollapsedColorFilter, - public val nodeNameStartPadding: Dp = 4.dp, + public val nodeNameStartPadding: Dp = 0.dp, public val nodeNameTextStyle: TextStyle = DefaultNodeTextStyle ) { public companion object { public val DefaultNodeTextStyle: TextStyle = TextStyle( fontWeight = FontWeight.Medium, - fontSize = 14.sp, - letterSpacing = 0.1.sp + fontSize = 12.sp ) } } diff --git a/bonsai-file-system/build.gradle.kts b/bonsai-file-system/build.gradle.kts index 377b0dd..0286a94 100644 --- a/bonsai-file-system/build.gradle.kts +++ b/bonsai-file-system/build.gradle.kts @@ -12,8 +12,7 @@ kotlin { val commonMain by getting { dependencies { api(projects.bonsaiCore) - implementation(libs.okio) - compileOnly(compose.runtime) + api(libs.okio) compileOnly(compose.foundation) compileOnly(compose.ui) compileOnly(compose.materialIconsExtended) diff --git a/bonsai-file-system/src/commonMain/kotlin/cafe/adriel/bonsai/filesystem/FileSystemNode.kt b/bonsai-file-system/src/commonMain/kotlin/cafe/adriel/bonsai/filesystem/FileSystemNode.kt index 5c426d8..6e4722e 100644 --- a/bonsai-file-system/src/commonMain/kotlin/cafe/adriel/bonsai/filesystem/FileSystemNode.kt +++ b/bonsai-file-system/src/commonMain/kotlin/cafe/adriel/bonsai/filesystem/FileSystemNode.kt @@ -5,6 +5,7 @@ import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.FolderOpen import androidx.compose.material.icons.outlined.InsertDriveFile import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp import cafe.adriel.bonsai.core.BonsaiStyle import cafe.adriel.bonsai.core.node.BranchNode import cafe.adriel.bonsai.core.node.Node @@ -19,6 +20,7 @@ internal data class FileSystemNodeScope( public fun FileSystemBonsaiStyle(): BonsaiStyle = BonsaiStyle( + nodeNameStartPadding = 4.dp, nodeCollapsedIcon = { node -> rememberVectorPainter( if (node is BranchNode) Icons.Outlined.Folder diff --git a/bonsai-json/.gitignore b/bonsai-json/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/bonsai-json/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/bonsai-json/build.gradle.kts b/bonsai-json/build.gradle.kts new file mode 100644 index 0000000..f3ebec9 --- /dev/null +++ b/bonsai-json/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") + id("com.android.library") + id("org.jetbrains.compose") + id("com.vanniktech.maven.publish") +} + +kotlinMultiplatform() + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + api(projects.bonsaiCore) + api(libs.serialization) + compileOnly(compose.foundation) + compileOnly(compose.ui) + } + } + } +} diff --git a/bonsai-json/consumer-rules.pro b/bonsai-json/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/bonsai-json/gradle.properties b/bonsai-json/gradle.properties new file mode 100644 index 0000000..e142096 --- /dev/null +++ b/bonsai-json/gradle.properties @@ -0,0 +1,2 @@ +POM_NAME=BonsaiJSON +POM_ARTIFACT_ID=bonsai-json \ No newline at end of file diff --git a/bonsai-json/src/androidMain/AndroidManifest.xml b/bonsai-json/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000..06d88da --- /dev/null +++ b/bonsai-json/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/bonsai-json/src/commonMain/kotlin/cafe/adriel/bonsai/json/JsonNode.kt b/bonsai-json/src/commonMain/kotlin/cafe/adriel/bonsai/json/JsonNode.kt new file mode 100644 index 0000000..f2ef017 --- /dev/null +++ b/bonsai-json/src/commonMain/kotlin/cafe/adriel/bonsai/json/JsonNode.kt @@ -0,0 +1,95 @@ +package cafe.adriel.bonsai.json + +import androidx.compose.ui.text.font.FontFamily +import cafe.adriel.bonsai.core.BonsaiStyle +import cafe.adriel.bonsai.core.node.Node +import cafe.adriel.bonsai.core.node.SimpleBranchNode +import cafe.adriel.bonsai.core.node.SimpleLeafNode +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.contentOrNull + +public fun JsonBonsaiStyle(): BonsaiStyle = + BonsaiStyle( + nodeNameTextStyle = BonsaiStyle.DefaultNodeTextStyle.copy( + fontFamily = FontFamily.Monospace + ) + ) + +public fun jsonNodes( + json: String +): List> = + jsonNodes( + key = "", + jsonElement = Json.Default.parseToJsonElement(json), + parent = null + ) + +private fun jsonNodes( + key: String, + jsonElement: JsonElement, + parent: Node? +): List> = + listOf( + when (jsonElement) { + is JsonNull -> JsonPrimitiveNode(key, jsonElement, parent) + is JsonPrimitive -> JsonPrimitiveNode(key, jsonElement, parent) + is JsonObject -> JsonObjectNode(key, jsonElement, parent) + is JsonArray -> JsonArrayNode(key, jsonElement, parent) + } + ) + +private fun JsonPrimitiveNode( + key: String, + jsonPrimitive: JsonPrimitive, + parent: Node? +) = + SimpleLeafNode( + content = jsonPrimitive, + name = "${getFormattedKey(key)}${getFormattedValue(jsonPrimitive)}", + parent = parent + ) + +private fun JsonObjectNode( + key: String, + jsonObject: JsonObject, + parent: Node? +) = + SimpleBranchNode( + content = jsonObject, + name = "${getFormattedKey(key)}{object}", + parent = parent, + children = { node -> + jsonObject.entries.flatMap { (name, jsonElement) -> + jsonNodes(name, jsonElement, node) + } + } + ) + +private fun JsonArrayNode( + key: String, + jsonArray: JsonArray, + parent: Node? +) = + SimpleBranchNode( + content = jsonArray, + name = "${getFormattedKey(key)}[array]", + parent = parent, + children = { node -> + jsonArray.flatMapIndexed { index, jsonElement -> + jsonNodes(index.toString(), jsonElement, node) + } + } + ) + +private fun getFormattedKey(key: String) = + if (key.isBlank()) "" + else "$key: " + +private fun getFormattedValue(jsonPrimitive: JsonPrimitive) = + if (jsonPrimitive.isString) "\"${jsonPrimitive.contentOrNull}\"" + else jsonPrimitive.contentOrNull diff --git a/gradle.properties b/gradle.properties index 985c59c..f17c9fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.mpp.enableGranularSourceSetsMetadata=true # Maven GROUP=cafe.adriel.bonsai -VERSION_NAME=1.0.0 +VERSION_NAME=1.1.0 POM_DESCRIPTION=A multiplatform tree view for Jetpack Compose POM_INCEPTION_YEAR=2022 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bafdff4..73e3f8b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ plugin-maven = "0.18.0" kotlin = "1.6.10" okio = "3.0.0" +serialization = "1.3.2" compose = "1.1.1" composeActivity = "1.4.0" @@ -17,9 +18,11 @@ plugin-ktlint = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = plugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "plugin-detekt" } plugin-maven = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "plugin-maven" } plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +plugin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } plugin-compose-multiplatform = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "composeMultiplatform" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } +serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } compose-activity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" } compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } @@ -27,4 +30,4 @@ compose-material = { module = "androidx.compose.material:material", version.ref compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } [bundles] -plugins = ["plugin-android", "plugin-ktlint", "plugin-detekt", "plugin-maven", "plugin-kotlin", "plugin-compose-multiplatform"] \ No newline at end of file +plugins = ["plugin-android", "plugin-ktlint", "plugin-detekt", "plugin-maven", "plugin-kotlin", "plugin-serialization", "plugin-compose-multiplatform"] \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 6960ec7..41cd0db 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -14,8 +14,8 @@ android { dependencies { implementation(projects.bonsaiCore) implementation(projects.bonsaiFileSystem) + implementation(projects.bonsaiJson) - implementation libs.okio implementation libs.compose.activity implementation libs.compose.material implementation libs.compose.material.icons diff --git a/settings.gradle.kts b/settings.gradle.kts index cdcb71b..345debb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ include( ":sample", ":bonsai-core", ":bonsai-file-system", + ":bonsai-json", ) enableFeaturePreview("VERSION_CATALOGS")