Skip to content

Commit

Permalink
feat: added support for react
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed May 31, 2024
1 parent fa7457a commit 1772b47
Show file tree
Hide file tree
Showing 46 changed files with 620 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
kotlin("multiplatform")
id("publication-convention")
}

kotlin {
explicitApi()

js(IR) {
browser()
}
}
6 changes: 5 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ androidx-navigation = "2.7.7"
androidx-lifecycle = "2.7.0"
ktgbotapi = "11.0.0"
mdi = "0.0.38"
react = "18.3.1-pre.754"

ksm = "0.0.37"
ksm = "0.0.40"

[libraries]

Expand All @@ -29,6 +30,9 @@ ktgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "ktgbotapi" }

mdi = { module = "app.meetacy.di:core", version.ref = "mdi" }

react = { module = "org.jetbrains.kotlin-wrappers:kotlin-react", version.ref = "react" }
react-dom = { module = "org.jetbrains.kotlin-wrappers:kotlin-react-dom", version.ref = "react" }

# gradle plugins
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlinx-serialization-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface ComposeController : NavigationController {
public companion object : StateControllerFactory {
override val type: KType = typeOf<ComposeController>()

override fun wrap(context: StateContext): PluginController {
override fun wrap(context: StateContext): ComposeController {
return object : ComposeController {
override val context = context
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ package ksm.navigation.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import ksm.StateController
import ksm.annotation.LibraryApi
import ksm.context.StateContext
import ksm.navigation.compose.plugin.ComposeSerializationStore
import ksm.navigation.compose.wrapper.ComposeWrapper
import ksm.navigation.serialization.restore
import ksm.plugin.factory.asController

@OptIn(LibraryApi::class)
@Composable
Expand All @@ -23,11 +21,15 @@ public fun rememberComposeController(
init = { ComposeSerializationStore() }
)
return remember(store) {
object : ComposeController.Builder {
val scope = object : ComposeController.Builder {
override var context: StateContext = StateContext.Empty
}.apply {
}

with(scope) {
composeRuntime(store, wrapper) { builder() }
context.restore()
}.context.asController()
}

ComposeController.wrap(scope.context)
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package ksm.navigation.compose

import androidx.compose.runtime.Composable
import ksm.StateController
import ksm.navigation.NavigationController
import ksm.navigation.compose.plugin.ComposePlugin
import ksm.navigation.state.route.StateBuilderScope
import ksm.navigation.route.StateBuilderScope
import ksm.plugin.plugin

public fun StateBuilderScope.Content(
block: @Composable NavigationController.() -> Unit
block: @Composable ComposeController.() -> Unit
) {
context.plugin(ComposePlugin).setContent(context, block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ package ksm.navigation.compose.example
import androidx.compose.runtime.Composable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import ksm.StateController
import ksm.navigation.NavigationController
import ksm.navigation.compose.Content
import ksm.navigation.compose.host.NavigationStateHost
import ksm.navigation.compose.host.ComposeStateHost
import ksm.navigation.compose.rememberComposeController
import ksm.navigation.state.route.StateRouteScope
import ksm.navigation.state.route.states
import ksm.navigation.state.data.receive
import ksm.navigation.state.name.named
import ksm.navigation.compose.route.ComposeRouteScope
import ksm.navigation.compose.route.named
import ksm.navigation.compose.route.states
import ksm.navigation.result.*
import ksm.navigation.state.data.receive
import ksm.viewmodel.viewModelController
import ksm.viewmodel.viewModelRuntime

private class MainViewModel {
val actions: Flow<Action> = emptyFlow()
Expand All @@ -40,7 +38,7 @@ private fun AppContent() {
details()
}

NavigationStateHost(
ComposeStateHost(
controller = controller,
startStateName = MAIN_SCREEN
)
Expand All @@ -52,7 +50,7 @@ private fun NavigationController.registerDetailsNavigator(
return registerNavigator(DETAILS_SCREEN, handler)
}

private fun StateRouteScope.main() = named(MAIN_SCREEN) {
private fun ComposeRouteScope.main() = named(MAIN_SCREEN) {
Content {
val launcher = registerDetailsNavigator { itemName ->
println("Item Picked! $itemName")
Expand All @@ -64,7 +62,7 @@ private fun StateRouteScope.main() = named(MAIN_SCREEN) {
}
}

private fun StateRouteScope.details() = named(DETAILS_SCREEN) {
private fun ComposeRouteScope.details() = named(DETAILS_SCREEN) {
Content {
val parameters: DetailsParameters = receive()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import ksm.plugin.factory.asController
import ksm.plugin.plugin

@Composable
public fun NavigationStateHost(
controller: StateController,
public fun ComposeStateHost(
controller: ComposeController,
startStateName: String,
modifier: Modifier = Modifier
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import ksm.navigation.compose.interceptor.ComposeInterceptor
internal class ComposeEntry(var interceptor: ComposeInterceptor?) : StateContext.Element {
override val key = ComposeEntry


var content: ComposeContent? = null

companion object : StateContext.Key<ComposeEntry>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ksm.navigation.compose.plugin

import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import ksm.annotation.LibraryApi
import ksm.annotation.MutateContext
import ksm.context.StateContext
import ksm.navigation.compose.ComposeController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ksm.navigation.compose.route

import androidx.compose.runtime.Composable
import ksm.context.StateContext
import ksm.navigation.compose.ComposeController
import ksm.navigation.compose.plugin.ComposePlugin
import ksm.navigation.route.StateBuilderScope
import ksm.plugin.plugin

public fun ComposeBuilderScope(context: StateContext): ComposeBuilderScope {
return object : ComposeBuilderScope {
override val context = context
}
}

public interface ComposeBuilderScope : StateBuilderScope

@Suppress("FunctionName")
public fun ComposeBuilderScope.Content(content: @Composable ComposeController.() -> Unit) {
context.plugin(ComposePlugin).setContent(
context = context,
content = content
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ksm.navigation.compose.route

import ksm.navigation.compose.ComposeController
import ksm.navigation.route.plugin.StateRoutePlugin
import ksm.plugin.plugin

public fun ComposeController.states(block: ComposeRouteScope.() -> Unit) {
context.plugin(StateRoutePlugin).states(context) { context ->
ComposeRouteScope(context).apply(block)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ksm.navigation.compose.route

import ksm.context.StateContext
import ksm.navigation.route.StateRouteScope
import ksm.navigation.state.name.interceptNamedRoute

public fun ComposeRouteScope(context: StateContext): ComposeRouteScope {
return object : ComposeRouteScope {
override val context = context
}
}

public interface ComposeRouteScope : StateRouteScope

public inline fun ComposeRouteScope.named(name: String, block: ComposeBuilderScope.() -> Unit) {
context.interceptNamedRoute(name) { ComposeBuilderScope(context).block() }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ksm.navigation.mdi

import app.meetacy.di.DI
import ksm.navigation.state.route.StateBuilderScope
import ksm.navigation.route.StateBuilderScope

public var StateBuilderScope.di: DI
get() = context.di
Expand Down
11 changes: 11 additions & 0 deletions navigation/navigation-react/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("js-browser-library-convention")
}

version = libs.versions.ksm.get()

dependencies {
commonMainApi(projects.navigation)
jsMainImplementation(libs.react)
jsMainImplementation(libs.react.dom)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ksm.navigation.react

import ksm.context.StateContext
import ksm.navigation.NavigationController
import ksm.plugin.factory.StateControllerFactory
import kotlin.reflect.KType
import kotlin.reflect.typeOf

public interface ReactController : NavigationController {
public interface Builder : NavigationController.Builder

public companion object : StateControllerFactory {
override val type: KType = typeOf<ReactController>()

override fun wrap(context: StateContext): ReactController {
return object : ReactController {
override val context = context
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ksm.navigation.react

import ksm.annotation.LibraryApi
import ksm.context.StateContext
import ksm.navigation.navigationRuntime
import ksm.navigation.react.observable.ObservableState
import ksm.navigation.react.plugin.ReactPlugin
import ksm.plugin.install

@LibraryApi
public inline fun ReactController.Builder.reactRuntime(
enableConfiguration: Boolean = true,
enableLifecycle: Boolean = true,
enableFinishOnce: Boolean = true,
enableStateName: Boolean = true,
enableStateParameters: Boolean = true,
enableStack: Boolean = true,
enableStateRoutePlugin: Boolean = true,
block: () -> Unit
) {
navigationRuntime(
controllerFactory = ReactController,
enableConfiguration = enableConfiguration,
enableLifecycle = enableLifecycle,
enableFinishOnce = enableFinishOnce,
enableStateName = enableStateName,
enableStateParameters = enableStateParameters,
enableStack = enableStack,
enableStateRoutePlugin = enableStateRoutePlugin
) {
install(ReactPlugin)
block()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ksm.navigation.react

import ksm.annotation.LibraryApi
import ksm.context.StateContext
import react.useMemo

/**
* Only works inside fc
*/
@OptIn(LibraryApi::class)
public fun useReactController(
enableConfiguration: Boolean = true,
enableLifecycle: Boolean = true,
enableFinishOnce: Boolean = true,
enableStateName: Boolean = true,
enableStateParameters: Boolean = true,
enableStack: Boolean = true,
enableStateRoutePlugin: Boolean = true,
builder: ReactController.Builder.() -> Unit = {}
): ReactController {
return useMemo(
enableConfiguration,
enableLifecycle,
enableFinishOnce,
enableStateName,
enableStateParameters,
enableStack,
enableStateRoutePlugin
) {
val scope = object : ReactController.Builder {
override var context: StateContext = StateContext.Empty
}
scope.reactRuntime(
enableConfiguration = enableConfiguration,
enableLifecycle = enableLifecycle,
enableFinishOnce = enableFinishOnce,
enableStateName = enableStateName,
enableStateParameters = enableStateParameters,
enableStack = enableStack,
enableStateRoutePlugin = enableStateRoutePlugin
) {
builder(scope)
}
ReactController.wrap(scope.context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ksm.navigation.react.example

import ksm.navigation.react.host.ReactStateHost
import ksm.navigation.react.host.ReactStateHostComponent
import ksm.navigation.react.route.component
import ksm.navigation.react.route.named
import ksm.navigation.react.route.states
import ksm.navigation.react.useReactController
import react.FC

private val exampleComponent = FC {
val controller = useReactController()

controller.states {
named("FirstState") {
component {

}
}
named("SecondState") {
component {

}
}
}

ReactStateHost(
controller = controller,
startStateName = "FirstState"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ksm.navigation.react.host

import ksm.navigation.react.ReactController
import org.w3c.dom.HTMLStyleElement
import react.Props

public external interface ReactControllerProps : Props {
public var controller: ReactController
public var startStateName: String
public var style: (HTMLStyleElement) -> Unit
}
Loading

0 comments on commit 1772b47

Please sign in to comment.