Skip to content
Merged
Show file tree
Hide file tree
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
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Less complexity, faster development, total visibility. That's Kick.
- [File Explorer](#file-explorer)
- [Layout (Beta)](#layout)
- [Overlay](#overlay)
- [Runner](#runner)
- [Advanced Module Configuration](#advanced-module-configuration)
- [Shortcuts](#shortcuts)
- [Launching Kick](#launching-kick)
Expand Down Expand Up @@ -433,6 +434,46 @@ Kick.init(context) {

Implement `OverlayProvider` to decide when your provider should run, which categories it contributes to, and how it updates values via `Kick.overlay.set` inside the supplied coroutine scope.

### Runner

Run ad‑hoc debug actions from inside Kick and render their results with pluggable renderers.

Built-in renderers:
- `JsonRunnerRenderer` — pretty-prints `String?` JSON (lenient, indented).
- `ImageRunnerRenderer` — shows `PlatformImage?` (Bitmap/UIImage/BufferedImage/ImageBitmap wrapper).
- `ObjectRunnerRenderer` — displays `Any?` via `toString()`.
You can plug in your own renderer by implementing `RunnerRenderer<T>` (with `setResult(T)` and `@Composable fun RenderContent(...)`) and passing it to `addCall` with the matching `T`.

Add dependencies:
```kotlin
// debug
implementation("ru.bartwell.kick:runner:1.0.0")
// release (no-op)
implementation("ru.bartwell.kick:runner-stub:1.0.0")
```

Initialize:
```kotlin
Kick.init(context) {
module(RunnerModule())
}
```

Register actions:
```kotlin
Kick.runner.addCall(
title = "Show JSON",
description = "Pretty print payload",
renderer = JsonRunnerRenderer()
) {
"""{"status":"ok","ts":${System.currentTimeMillis()}}"""
}
```

Platform images:
- Create with `PlatformImage.fromImageBitmap(imageBitmap)` or `PlatformImage.fromNative(native)` (Bitmap/UIImage/BufferedImage).
- Render via `ImageRunnerRenderer`.

### Advanced Module Configuration

You don't need to add all the available modules. Just include the ones you need. Here only logging and network inspection are enabled:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ public enum class ModuleDescription(
title = "Firebase Cloud Messaging",
description = "Inspect FCM tokens, installation id, and delivered pushes."
),
RUNNER(
title = "Runner",
description = "Register debug actions and view their rendered results."
),
}
78 changes: 78 additions & 0 deletions module/logging/runner-stub/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.publish.plugin)
id("publish-convention")
}

group = "ru.bartwell.kick"
version = extra["libraryVersionName"] as String

kotlin {
androidTarget {
compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}
}
}

wasmJs {
browser()
}

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "runner_stub"
isStatic = true
}
}

jvm()

sourceSets {
commonMain.dependencies {
implementation(projects.mainCore)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(libs.decompose)
implementation(libs.decompose.extensions.compose)
implementation(libs.decompose.essenty.lifecycle.coroutines)
implementation(libs.kotlinx.coroutines.core)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
androidMain.dependencies {}
appleMain.dependencies {}
jvmMain.dependencies {}
}

explicitApi()
}

android {
namespace = "ru.bartwell.kick.module.runner_stub"
compileSdk = 35

defaultConfig {
minSdk = 24
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.bartwell.kick.module.runner

import ru.bartwell.kick.Kick
import ru.bartwell.kick.module.runner.core.data.RunnerRenderer
import ru.bartwell.kick.module.runner.core.params.RunnerParameter
import ru.bartwell.kick.module.runner.core.params.RunnerParameters

public val Kick.Companion.runner: RunnerAccessor
get() = RunnerAccessor

@Suppress("UnusedParameter", "EmptyFunctionBlock")
public object RunnerAccessor {
public fun clear() {}
public fun <T> addCall(
title: String,
description: String? = null,
renderer: RunnerRenderer<T>,
block: suspend () -> T
) {}

public fun <T> addCall(
title: String,
description: String? = null,
params: List<RunnerParameter<*>>,
renderer: RunnerRenderer<T>,
block: suspend (RunnerParameters) -> T,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ru.bartwell.kick.module.runner

import androidx.compose.runtime.Composable
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.StackNavigation
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import ru.bartwell.kick.core.component.Child
import ru.bartwell.kick.core.component.Config
import ru.bartwell.kick.core.component.StubConfig
import ru.bartwell.kick.core.data.Module
import ru.bartwell.kick.core.data.ModuleDescription

@Suppress("EmptyFunctionBlock", "unused")
public class RunnerModule : Module {

override val description: ModuleDescription = ModuleDescription.RUNNER
override val startConfig: Config = StubConfig(description)

override fun getComponent(
componentContext: ComponentContext,
nav: StackNavigation<Config>,
config: Config,
): Child<*>? = null

@Composable
override fun Content(instance: Child<*>) {
}

override fun registerSubclasses(builder: PolymorphicModuleBuilder<Config>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.bartwell.kick.module.runner.core.data

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap

public class PlatformImage internal constructor() {
public companion object
}

@Composable
@Suppress("EmptyFunctionBlock", "UnusedParameter")
public fun PlatformImage.Content(modifier: Modifier = Modifier) {
}

@Suppress("UnusedParameter", "FunctionOnlyReturningConstant")
public fun PlatformImage.Companion.fromImageBitmap(image: ImageBitmap?): PlatformImage? = null

@Suppress("UnusedParameter", "FunctionOnlyReturningConstant")
public fun PlatformImage.Companion.fromNative(native: Any?): PlatformImage? = null
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.bartwell.kick.module.runner.core.data

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

public interface RunnerRenderer<T> {
public fun setResult(result: T)

@Composable
public fun RenderContent(modifier: Modifier = Modifier)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.bartwell.kick.module.runner.core.params

public data class RunnerParameter<T>(
val id: String,
val title: String,
val description: String? = null,
val type: RunnerParameterType<T>,
val required: Boolean = false,
val defaultValue: T? = null,
)

public sealed interface RunnerParameterType<T> {
public object BooleanType : RunnerParameterType<Boolean>
public data class IntType(val min: Int? = null, val max: Int? = null) : RunnerParameterType<Int>
public data class LongType(val min: Long? = null, val max: Long? = null) : RunnerParameterType<Long>
public data class FloatType(val min: Float? = null, val max: Float? = null) : RunnerParameterType<Float>
public data class DoubleType(val min: Double? = null, val max: Double? = null) : RunnerParameterType<Double>
public data class StringType(val multiline: Boolean = false) : RunnerParameterType<String>
public data class SingleChoice<T : Any>(val options: List<T>) : RunnerParameterType<T>
public data class MultiChoice<T : Any>(val options: List<T>) : RunnerParameterType<Set<T>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.bartwell.kick.module.runner.core.params

@Suppress("UnusedPrivateProperty")
public class RunnerParameters internal constructor(
private val values: Map<String, Any?>,
) {
@Suppress("UnusedParameter", "FunctionOnlyReturningConstant")
public fun <T> get(id: String): T? = null

internal companion object {
val EMPTY: RunnerParameters = RunnerParameters(emptyMap())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.bartwell.kick.module.runner.core.renderer

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import ru.bartwell.kick.module.runner.core.data.PlatformImage
import ru.bartwell.kick.module.runner.core.data.RunnerRenderer

@Suppress("EmptyFunctionBlock", "UnusedParameter")
public class ImageRunnerRenderer : RunnerRenderer<PlatformImage?> {
override fun setResult(result: PlatformImage?) {
}

@Composable
override fun RenderContent(modifier: Modifier) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.bartwell.kick.module.runner.core.renderer

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import ru.bartwell.kick.module.runner.core.data.RunnerRenderer

@Suppress("EmptyFunctionBlock", "UnusedParameter")
public class JsonRunnerRenderer : RunnerRenderer<String?> {
override fun setResult(result: String?) {
}

@Composable
override fun RenderContent(modifier: Modifier) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.bartwell.kick.module.runner.core.renderer

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import ru.bartwell.kick.module.runner.core.data.RunnerRenderer

@Suppress("EmptyFunctionBlock", "UnusedParameter")
public class ObjectRunnerRenderer : RunnerRenderer<Any?> {
override fun setResult(result: Any?) {
}

@Composable
override fun RenderContent(modifier: Modifier) {
}
}
Loading