@@ -4,22 +4,36 @@ import androidx.compose.material3.Text
44import androidx.compose.runtime.Composable
55import androidx.compose.ui.graphics.Color
66import kotlinx.serialization.json.JsonObject
7+ import kotlinx.serialization.json.JsonPrimitive
8+ import kotlinx.serialization.json.booleanOrNull
9+ import kotlinx.serialization.json.doubleOrNull
10+ import kotlinx.serialization.json.intOrNull
11+ import kotlinx.serialization.json.longOrNull
12+
13+ // / Typed access to a custom composable's props, so app authors read values
14+ // / without touching the underlying JSON layer (or depending on it).
15+ class Props internal constructor(private val json : JsonObject ) {
16+ fun string (key : String ): String? = (json[key] as ? JsonPrimitive )?.content
17+ fun double (key : String ): Double? = (json[key] as ? JsonPrimitive )?.doubleOrNull
18+ fun float (key : String ): Float? = double(key)?.toFloat()
19+ fun int (key : String ): Int? = (json[key] as ? JsonPrimitive )?.intOrNull
20+ fun bool (key : String ): Boolean? = (json[key] as ? JsonPrimitive )?.booleanOrNull
21+ // / A color passed from Swift as `PropValue.color(_:)` (an ARGB int).
22+ fun color (key : String ): Color ? = (json[key] as ? JsonPrimitive )?.longOrNull?.let { Color (it.toInt()) }
23+ }
724
825// / The library's single extension point: Kotlin registers named composables;
9- // / Swift emits a `Composable (name:props:)` node referencing one. A registered
10- // / factory receives the node's props and its rendered children slot . Entries
11- // / registered from common code work on both Android and desktop; a factory
12- // / that wraps an Android-only view simply isn't registered on desktop.
26+ // / Swift emits a `ComposableView (name:props:)` node referencing one. A factory
27+ // / receives typed props and a slot rendering the SwiftUI child content . Entries
28+ // / registered from common code work on both Android and desktop; a factory that
29+ // / wraps an Android-only view simply isn't registered on desktop.
1330object ComposableRegistry {
1431
15- fun interface Factory {
16- @Composable
17- fun Content (props : JsonObject , children : @Composable () -> Unit )
18- }
19-
20- private val factories = mutableMapOf<String , Factory >()
32+ // A registered factory: given props and a slot rendering the SwiftUI child
33+ // content, it emits composables. Stored as a plain composable function type.
34+ private val factories = mutableMapOf<String , @Composable (props: Props , children: @Composable () -> Unit ) - > Unit > ()
2135
22- fun register (name : String , factory : Factory ) {
36+ fun register (name : String , factory : @Composable (props : Props , children : @Composable () -> Unit ) -> Unit ) {
2337 factories[name] = factory
2438 }
2539
@@ -28,9 +42,13 @@ object ComposableRegistry {
2842 val name = node.string(" name" ) ? : return
2943 val factory = factories[name]
3044 if (factory != null ) {
31- factory.Content (node.props) {
45+ factory(Props (node.props)) {
46+ // RenderChild, not the public Render: a nested call to a public
47+ // composable from this dynamically-invoked factory slot has its
48+ // restart group skipped by the runtime and silently renders
49+ // nothing.
3250 for (child in node.children) {
33- Render (child)
51+ RenderChild (child)
3452 }
3553 }
3654 } else {
0 commit comments