Skip to content

Commit 6f0beba

Browse files
committed
Wire gesture and lifecycle modifiers to Compose effects
1 parent 48491fe commit 6f0beba

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

  • Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui

Demo/swiftui/src/commonMain/kotlin/com/pureswift/swiftui/Render.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.pureswift.swiftui
22

33
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
45
import androidx.compose.foundation.rememberScrollState
56
import androidx.compose.foundation.horizontalScroll
67
import androidx.compose.foundation.verticalScroll
@@ -50,6 +51,7 @@ import androidx.compose.material3.TopAppBar
5051
import androidx.compose.material.icons.Icons
5152
import androidx.compose.material.icons.automirrored.filled.ArrowBack
5253
import androidx.compose.runtime.Composable
54+
import androidx.compose.runtime.DisposableEffect
5355
import androidx.compose.runtime.LaunchedEffect
5456
import androidx.compose.runtime.getValue
5557
import androidx.compose.runtime.key
@@ -83,13 +85,15 @@ import androidx.compose.foundation.shape.RoundedCornerShape
8385
@Composable
8486
fun Render(node: ViewNode) {
8587
key(node.id) {
88+
RenderEffects(node)
8689
when (node.type) {
8790
"Text" -> RenderText(node)
8891

8992
"Button" -> {
9093
val onTap = node.long("onTap")
9194
Button(
9295
onClick = { onTap?.let { SwiftBridge.sink.invokeVoid(it) } },
96+
enabled = !node.isDisabled(),
9397
modifier = node.composeModifiers(),
9498
) {
9599
RenderChildren(node)
@@ -334,6 +338,40 @@ private fun zStackAlignment(node: ViewNode): Alignment {
334338
}
335339
}
336340

341+
// Lifecycle modifiers (onAppear/onDisappear/task/onChange) install Compose
342+
// effects rather than folding into the Modifier chain. Keyed within the node's
343+
// `key(id)` scope, so onAppear fires once on entry and onDisappear on exit,
344+
// surviving recomposition.
345+
@Composable
346+
private fun RenderEffects(node: ViewNode) {
347+
val onAppear = node.modifiers.firstOrNull { it.kind == "onAppear" }?.args?.long("action")
348+
val onDisappear = node.modifiers.firstOrNull { it.kind == "onDisappear" }?.args?.long("action")
349+
val task = node.modifiers.firstOrNull { it.kind == "task" }?.args?.long("action")
350+
val onChange = node.modifiers.firstOrNull { it.kind == "onChange" }
351+
352+
if (onAppear != null || onDisappear != null) {
353+
DisposableEffect(Unit) {
354+
onAppear?.let { SwiftBridge.sink.invokeVoid(it) }
355+
onDispose { onDisappear?.let { SwiftBridge.sink.invokeVoid(it) } }
356+
}
357+
}
358+
if (task != null) {
359+
LaunchedEffect(Unit) { SwiftBridge.sink.invokeVoid(task) }
360+
}
361+
if (onChange != null) {
362+
val token = onChange.args.string("token")
363+
val action = onChange.args.long("action")
364+
// skip the initial composition; fire when the token changes thereafter
365+
var primed by remember(node.id) { mutableStateOf(false) }
366+
LaunchedEffect(token) {
367+
if (primed) action?.let { SwiftBridge.sink.invokeVoid(it) } else primed = true
368+
}
369+
}
370+
}
371+
372+
private fun ViewNode.isDisabled(): Boolean =
373+
modifiers.any { it.kind == "disabled" && (it.args["value"] as? kotlinx.serialization.json.JsonPrimitive)?.content == "true" }
374+
337375
// Text-styling modifiers describe attributes of the Text composable itself,
338376
// not the layout Modifier chain, so they are read off the node here and passed
339377
// as `Text(...)` parameters. Later (innermost) entries win for scalar
@@ -465,6 +503,18 @@ internal fun ViewNode.composeModifiers(): Modifier {
465503

466504
"opacity" -> modifier.alpha((entry.args.double("opacity") ?: 1.0).toFloat())
467505

506+
"onTapGesture" -> {
507+
val id = entry.args.long("action")
508+
if (id != null) modifier.clickable { SwiftBridge.sink.invokeVoid(id) } else modifier
509+
}
510+
511+
// Dim disabled content; controls also drop interactivity via their
512+
// own `enabled` parameter (e.g. Button).
513+
"disabled" -> {
514+
val off = (entry.args["value"] as? kotlinx.serialization.json.JsonPrimitive)?.content == "true"
515+
if (off) modifier.alpha(0.38f) else modifier
516+
}
517+
468518
else -> modifier // unknown modifier: ignore, never crash rendering
469519
}
470520
}

0 commit comments

Comments
 (0)