@@ -158,6 +158,7 @@ import androidx.compose.ui.semantics.selected
158158import androidx.compose.ui.semantics.Role
159159import androidx.compose.ui.semantics.role
160160import androidx.compose.ui.platform.testTag
161+ import androidx.compose.ui.platform.LocalUriHandler
161162import androidx.compose.ui.graphics.Color
162163import androidx.compose.ui.layout.ContentScale
163164import androidx.compose.ui.graphics.ImageBitmap
@@ -342,6 +343,8 @@ private fun RenderResolved(node: ViewNode) {
342343 }
343344 }
344345
346+ " Link" -> RenderLink (node)
347+
345348 " GeometryReader" -> RenderGeometryReader (node)
346349
347350 " LazyVStack" -> RenderLazyStack (node, vertical = true )
@@ -577,6 +580,27 @@ private fun RenderGeometryReader(node: ViewNode) {
577580 }
578581}
579582
583+ // Handing the address to the platform's UriHandler keeps the whole hop out of
584+ // Swift — no callback, no bridge entry. The label takes the accent colour so it
585+ // reads as a link, unless something upstream already tinted it.
586+ @Composable
587+ private fun RenderLink (node : ViewNode ) {
588+ val url = node.string(" url" )
589+ val handler = LocalUriHandler .current
590+ val accent = LocalTint .current ? : MaterialTheme .colorScheme.primary
591+ Row (
592+ verticalAlignment = Alignment .CenterVertically ,
593+ modifier = node.composeModifiers().clickable(enabled = url != null && node.isEnabled()) {
594+ // a malformed or unhandled address must not take the app down
595+ url?.let { runCatching { handler.openUri(it) } }
596+ },
597+ ) {
598+ CompositionLocalProvider (LocalInheritedColor provides accent) {
599+ RenderChildren (node)
600+ }
601+ }
602+ }
603+
580604private fun ViewNode.isPresentation (): Boolean =
581605 type == " Sheet" || type == " Alert" || type == " ConfirmationDialog" || type == " ToolbarItem"
582606
0 commit comments