Skip to content

Commit

Permalink
feat: json integration
Browse files Browse the repository at this point in the history
  • Loading branch information
adrielcafe committed Apr 15, 2022
1 parent 2fb065c commit a737219
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 13 deletions.
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<Path>(
nodes = fileSystemNodes(
Expand All @@ -81,7 +81,7 @@ val tree = rememberTree<Path>(

Bonsai(
tree = tree,
// Custom style to show file and directory icons
// Custom style
style = FileSystemBonsaiStyle()
)
```
Expand All @@ -90,6 +90,26 @@ Output:

<img width=300 src="https://user-images.githubusercontent.com/2512298/163184371-a5a38003-44d9-4daa-8f41-6ee3914611f1.png">

### JSON integration
Import `cafe.adriel.bonsai:bonsai-json` module to use it.

```kotlin
val tree = rememberTree<Path>(
// Sample JSON from https://rickandmortyapi.com/api/character
nodes = jsonNodes(json)
)

Bonsai(
tree = tree,
// Custom style
style = JsonBonsaiStyle()
)
```

Output:

<img width=350 src="https://user-images.githubusercontent.com/2512298/163498419-f555d3df-e75d-4e88-9d87-7f29b14e1214.png">

### DSL
Looking for a simpler and less verbose way to create a tree? Here's a handy DSL for you.
```kotlin
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ public data class BonsaiStyle<T>(
public val nodeCollapsedColorFilter: ColorFilter? = null,
public val nodeExpandedIcon: NodeIcon<T> = 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
)
}
}
Expand Down
3 changes: 1 addition & 2 deletions bonsai-file-system/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,6 +20,7 @@ internal data class FileSystemNodeScope(

public fun FileSystemBonsaiStyle(): BonsaiStyle<Path> =
BonsaiStyle(
nodeNameStartPadding = 4.dp,
nodeCollapsedIcon = { node ->
rememberVectorPainter(
if (node is BranchNode) Icons.Outlined.Folder
Expand Down
1 change: 1 addition & 0 deletions bonsai-json/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
22 changes: 22 additions & 0 deletions bonsai-json/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Empty file added bonsai-json/consumer-rules.pro
Empty file.
2 changes: 2 additions & 0 deletions bonsai-json/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
POM_NAME=BonsaiJSON
POM_ARTIFACT_ID=bonsai-json
2 changes: 2 additions & 0 deletions bonsai-json/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="cafe.adriel.bonsai.json"/>
Original file line number Diff line number Diff line change
@@ -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<JsonElement> =
BonsaiStyle(
nodeNameTextStyle = BonsaiStyle.DefaultNodeTextStyle.copy(
fontFamily = FontFamily.Monospace
)
)

public fun jsonNodes(
json: String
): List<Node<JsonElement>> =
jsonNodes(
key = "",
jsonElement = Json.Default.parseToJsonElement(json),
parent = null
)

private fun jsonNodes(
key: String,
jsonElement: JsonElement,
parent: Node<JsonElement>?
): List<Node<JsonElement>> =
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<JsonElement>?
) =
SimpleLeafNode(
content = jsonPrimitive,
name = "${getFormattedKey(key)}${getFormattedValue(jsonPrimitive)}",
parent = parent
)

private fun JsonObjectNode(
key: String,
jsonObject: JsonObject,
parent: Node<JsonElement>?
) =
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<JsonElement>?
) =
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
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -17,14 +18,16 @@ 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" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
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"]
plugins = ["plugin-android", "plugin-ktlint", "plugin-detekt", "plugin-maven", "plugin-kotlin", "plugin-serialization", "plugin-compose-multiplatform"]
2 changes: 1 addition & 1 deletion sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include(
":sample",
":bonsai-core",
":bonsai-file-system",
":bonsai-json",
)

enableFeaturePreview("VERSION_CATALOGS")
Expand Down

0 comments on commit a737219

Please sign in to comment.