Skip to content

New gallery UI implementation #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/smokebuild.yaml
Original file line number Diff line number Diff line change
@@ -27,6 +27,10 @@ jobs:
- name: Publish to Maven Local
run: ./gradlew publishToMavenLocal

- name: Build gallery-demo
run: |
./gradlew :gallery-demo:wasmJsBrowserDevelopmentExecutableDistribution :gallery-demo:packageReleaseUberJarForCurrentOS

- name: Build Stories for Wasm target
run: |
cd examples
122 changes: 122 additions & 0 deletions gallery-demo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import org.jetbrains.compose.reload.ComposeHotRun
Copy link
Member

Choose a reason for hiding this comment

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

I have a small question related to this module. We have the example one, which contains an example of the demo. Was it intentional to create a new one instead of re-using the example?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the existing example requires a publication of all the modules to mavenLocal.
It was more convenient to iterate and test the gallery changes without a publication.

The existing example module is still useful to test the integration of all modules - we have CI checks with it.
We have something similar is Compose Multiplatform too: a demo right in the core project - it uses project dependencies, and a set of side projects which integrate everyting as published module dependencies.

Copy link
Member

Choose a reason for hiding this comment

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

In the future, we need to figure out how to organize the structure in the repo so that it does not confuse us or the new contributors.

import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.serialization)
id("org.jetbrains.compose.hot-reload") version "1.0.0-alpha03"
}

class StorytaleCompilerPlugin : KotlinCompilerPluginSupportPlugin {
override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
return kotlinCompilation.project.provider { emptyList() }
}

override fun getCompilerPluginId(): String {
return "org.jetbrains.compose.compiler.plugins.storytale"
}

override fun getPluginArtifact(): SubpluginArtifact {
return SubpluginArtifact("org.jetbrains.compose.storytale", "local-compiler-plugin")
}

override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
return kotlinCompilation.target.platformType in setOf(
org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm,
org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.wasm,
)
}
}

apply<StorytaleCompilerPlugin>()

configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("org.jetbrains.compose.storytale:local-compiler-plugin"))
.using(project(":modules:compiler-plugin"))
}
}

kotlin {
js {
browser()
binaries.executable()
}
wasmJs {
moduleName = "gallery-demo"
browser {
commonWebpackConfig {
outputFileName = "gallery-demo.js"
}
}
binaries.executable()
}

jvm("desktop")

applyDefaultHierarchyTemplate()

sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.navigation.compose)
implementation(libs.compose.highlights)
implementation(libs.kotlinx.serialization.json)
implementation(projects.modules.runtimeApi)
implementation(projects.modules.gallery)
implementation("org.jetbrains.compose.material3.adaptive:adaptive:1.1.0-beta01")
}
}

val desktopMain by getting {
dependsOn(commonMain)
dependencies {
implementation(compose.desktop.currentOs)
}
}
}

@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
freeCompilerArgs = listOf(
"-opt-in=androidx.compose.animation.ExperimentalSharedTransitionApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi",
"-Xexpect-actual-classes",
)
}
}

compose.desktop {
application {
mainClass = "storytale.gallery.demo.MainKt"
}
}

composeCompiler {
featureFlags.add(ComposeFeatureFlag.OptimizeNonSkippingGroups)
}

tasks.register<ComposeHotRun>("runHot") {
mainClass.set("storytale.gallery.demo.MainKt")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
@file:Suppress("ktlint:standard:property-naming")

package storytale.gallery.demo

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.LargeFloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.storytale.story

val `Floating Action Buttons` by story {
val Density by parameter(LocalDensity.current)
val `Container color` by parameter(MaterialTheme.colorScheme.primary)
val bgColor = `Container color`

CompositionLocalProvider(LocalDensity provides Density) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
SmallFloatingActionButton(onClick = {}, containerColor = bgColor) {
Icon(imageVector = Icons.Default.Add, contentDescription = null)
}
FloatingActionButton(onClick = {}, containerColor = bgColor) {
Icon(imageVector = Icons.Default.Add, contentDescription = null)
}
ExtendedFloatingActionButton(onClick = {}, containerColor = bgColor) {
Icon(imageVector = Icons.Default.AddCircle, contentDescription = null)
Spacer(Modifier.padding(4.dp))
Text("Extended")
}
LargeFloatingActionButton(onClick = {}, containerColor = bgColor) {
Text("Large")
}
}
}
}

val `Segmented buttons` by story {
val selectedIndex = remember { mutableIntStateOf(0) }

SingleChoiceSegmentedButtonRow {
repeat(3) { index ->
SegmentedButton(
selected = index == selectedIndex.value,
onClick = { selectedIndex.value = index },
shape = SegmentedButtonDefaults.itemShape(index, 3),
) {
Text("Button $index", modifier = Modifier.padding(4.dp))
}
}
}
}

val `Common buttons` by story {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
ElevatedButton(onClick = {}) {
Text("Elevated Button")
}

Button(onClick = {}) {
Text("Filled", softWrap = false)
}

FilledTonalButton(onClick = {}) {
Text("Tonal", softWrap = false)
}

OutlinedButton(onClick = {}) {
Text("Outlined", softWrap = false)
}

TextButton(onClick = {}) {
Text("Text", softWrap = false)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package storytale.gallery.demo

import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.storytale.story

val foodEmojiList = listOf(
"Apple 🍎",
"Banana 🍌",
"Cherry 🍒",
"Grapes 🍇",
"Strawberry 🍓",
"Watermelon 🍉",
"Pineapple 🍍",
"Pizza 🍕",
"Burger 🍔",
"Fries 🍟",
"Ice Cream 🍦",
"Cake 🍰",
"Coffee ☕",
"Beer 🍺",
)

val `List Parameters` by story {
val food by parameter(foodEmojiList)

Button(onClick = {}) {
Text(food)
}
}

enum class PrimaryButtonSize {
Small,
Medium,
Large,
}

val `Enum Parameters` by story {
val size by parameter(PrimaryButtonSize.Medium, label = null)

Button(
onClick = {},
modifier = Modifier.size(
when (size) {
PrimaryButtonSize.Small -> 90.dp
PrimaryButtonSize.Medium -> 120.dp
PrimaryButtonSize.Large -> 150.dp
},
),
) {
Text(size.toString())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@file:Suppress("ktlint:standard:property-naming")

package storytale.gallery.demo

import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import org.jetbrains.compose.storytale.story

val Button by story {
val Label by parameter("Click Me")
val Enabled by parameter(true)
val bgColorAlpha by parameter(1f)

Button(
enabled = Enabled,
onClick = {},
colors = ButtonDefaults.buttonColors().copy(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = bgColorAlpha),
),
) {
Text(Label)
}
}

val Checkbox by story {
var checked by parameter(false)
Checkbox(checked, onCheckedChange = { checked = it })
}

val Switch by story {
var checked by parameter(false)
Switch(checked, onCheckedChange = { checked = it })
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jetbrains.compose.storytale.generated

import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
import org.jetbrains.compose.reload.DevelopmentEntryPoint
import org.jetbrains.compose.storytale.gallery.material3.StorytaleGalleryApp

// To let the Storytale compiler plugin add the initializations for stories
@Suppress("ktlint:standard:function-naming")
fun MainViewController() {
singleWindowApplication(
state = WindowState(width = 800.dp, height = 800.dp),
) {
DevelopmentEntryPoint {
StorytaleGalleryApp()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package storytale.gallery.demo

import org.jetbrains.compose.storytale.generated.MainViewController

fun main() {
MainViewController()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.jetbrains.compose.storytale.generated

@Suppress("ktlint:standard:function-naming")
fun MainViewController() {}
33 changes: 33 additions & 0 deletions gallery-demo/src/wasmJsMain/kotlin/storytale/gallery/demo/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package storytale.gallery.demo

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.window.ComposeViewport
import androidx.navigation.ExperimentalBrowserHistoryApi
import androidx.navigation.bindToNavigation
import androidx.navigation.compose.rememberNavController
import kotlinx.browser.window
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.preloadFont
import org.jetbrains.compose.storytale.gallery.material3.StorytaleGalleryApp
import org.jetbrains.compose.storytale.gallery.story.code.JetBrainsMonoRegularRes
import org.jetbrains.compose.storytale.generated.MainViewController

@OptIn(ExperimentalResourceApi::class, ExperimentalBrowserHistoryApi::class)
fun main() {
MainViewController() // Storytale compiler will initialize the stories

val useEmbedded = window.location.search.contains("embedded=true")

ComposeViewport(viewportContainerId = "composeApplication") {
val hasResourcePreloadCompleted = preloadFont(JetBrainsMonoRegularRes).value != null
val navHostController = rememberNavController()

if (hasResourcePreloadCompleted) {
StorytaleGalleryApp(isEmbedded = useEmbedded, navHostController)

LaunchedEffect(Unit) {
window.bindToNavigation(navHostController)
}
}
}
}
Loading