Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b7d2a09
Create InteropTest file
svastven May 13, 2025
0796110
Add initial interop tests
svastven Jun 2, 2025
8f4575d
Fix delay to Long
svastven Jun 2, 2025
71e2490
Merge branch 'jb-main' into svastven/test-interop
svastven Aug 13, 2025
5e7d504
Merge branch 'jb-main' into svastven/test-interop
svastven Aug 18, 2025
1ae2e6e
Merge branch 'jb-main' into svastven/test-interop
svastven Sep 9, 2025
50fbbf3
Split interop tests into two test suites
svastven Sep 9, 2025
b9a102a
Merge branch 'svastven/test-interop' into svastven/sizing-interop-vie…
svastven Oct 14, 2025
288c7ee
Move measurePolicy to abstract val in InteropViewHolder.skiko.kt
svastven Oct 15, 2025
e674e8c
Override measurePolicy in SwingInteropViewHolder
svastven Oct 15, 2025
8518a66
Override measurePolicy in WebInteropElementHolder
svastven Oct 15, 2025
e03d083
Create UIKitInteropViewMeasurePolicy
svastven Oct 15, 2025
72b1404
Override measurePolicy in UIKitInteropViewControllerHolder and UIKitI…
svastven Oct 15, 2025
e65cca5
Add tests for sizing of interop views
svastven Oct 15, 2025
feb0237
Merge branch 'jb-main' into svastven/sizing-interop-views-in-compose
svastven Oct 15, 2025
4f6dcd5
Increase delay in interaction mode test
svastven Oct 15, 2025
d9a943d
Remove unused imports
svastven Oct 15, 2025
408536d
Test for matching UILabel frame with UIKitView bounds
svastven Oct 15, 2025
566c404
Remove resolved comment
svastven Oct 15, 2025
6031d34
Make targetSize calculation lazy
svastven Oct 15, 2025
1f38096
Add trailing commas
svastven Oct 17, 2025
49442a8
Move targetSize to measuredSize lazy calculation in UIKitInteropViewM…
svastven Oct 17, 2025
9e74a1f
Add trailing commas
svastven Oct 17, 2025
596e68f
Dump API
svastven Oct 17, 2025
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
6 changes: 6 additions & 0 deletions compose/ui/ui/api/ui.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -5177,6 +5177,9 @@ final val androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitI
// Targets: [ios]
final val androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop // androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop|#static{}androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop[0]

// Targets: [ios]
final val androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop // androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop|#static{}androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop[0]

// Targets: [ios]
final val androidx.compose.ui.window/androidx_compose_ui_window_ComposeSceneKeyboardOffsetManager$stableprop // androidx.compose.ui.window/androidx_compose_ui_window_ComposeSceneKeyboardOffsetManager$stableprop|#static{}androidx_compose_ui_window_ComposeSceneKeyboardOffsetManager$stableprop[0]

Expand Down Expand Up @@ -5372,6 +5375,9 @@ final fun androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitI
// Targets: [ios]
final fun androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop_getter(): kotlin/Int // androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop_getter|androidx_compose_ui_viewinterop_UIKitInteropViewHolder$stableprop_getter(){}[0]

// Targets: [ios]
final fun androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop_getter(): kotlin/Int // androidx.compose.ui.viewinterop/androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop_getter|androidx_compose_ui_viewinterop_UIKitInteropViewMeasurePolicy$stableprop_getter(){}[0]

// Targets: [ios]
final fun androidx.compose.ui.window/ComposeUIViewController(kotlin/Function1<androidx.compose.ui.uikit/ComposeUIViewControllerConfiguration, kotlin/Unit> = ..., kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>): platform.UIKit/UIViewController // androidx.compose.ui.window/ComposeUIViewController|ComposeUIViewController(kotlin.Function1<androidx.compose.ui.uikit.ComposeUIViewControllerConfiguration,kotlin.Unit>;kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}[0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,12 @@ internal class SwingInteropViewHolder<T : Component>(
override val group: SwingInteropViewGroup,
focusSwitcher: InteropFocusSwitcher,
compositeKeyHashCode: CompositeKeyHashCode,
measurePolicy: MeasurePolicy
override val measurePolicy: MeasurePolicy,
) : TypedInteropViewHolder<T>(
factory = factory,
interopContainer = container,
group = group,
compositeKeyHashCode = compositeKeyHashCode,
measurePolicy = measurePolicy
), ClipRectangle {

private var clipBounds: IntRect? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.currentCompositeKeyHashCode
import androidx.compose.ui.Modifier
import androidx.compose.ui.UiComposable
import androidx.compose.ui.layout.MeasurePolicy
import androidx.compose.ui.materialize
import androidx.compose.ui.node.ComposeUiNode.Companion.SetCompositeKeyHash
import androidx.compose.ui.node.ComposeUiNode.Companion.SetResolvedCompositionLocals
Expand All @@ -47,12 +46,10 @@ internal abstract class TypedInteropViewHolder<T : InteropView>(
interopContainer: InteropContainer,
group: InteropViewGroup,
compositeKeyHashCode: CompositeKeyHashCode,
measurePolicy: MeasurePolicy
) : InteropViewHolder(
interopContainer,
group,
compositeKeyHashCode,
measurePolicy
) {
@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("interopView")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ internal abstract class InteropViewHolder(
val container: InteropContainer,
open val group: InteropViewGroup,
private val compositeKeyHashCode: CompositeKeyHashCode,
measurePolicy: MeasurePolicy
) : InteropViewFactoryHolder() {
private var onModifierChanged: (() -> Unit)? = null

Expand All @@ -63,6 +62,8 @@ internal abstract class InteropViewHolder(
}
}

protected abstract val measurePolicy: MeasurePolicy

private var hasUpdateBlock = false

var update: () -> Unit = {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.interop

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.findNodeWithTag
import androidx.compose.ui.test.runUIKitInstrumentedTest
import androidx.compose.ui.test.utils.up
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.UIKitInteropInteractionMode
import androidx.compose.ui.viewinterop.UIKitInteropProperties
import androidx.compose.ui.viewinterop.UIKitView
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.readValue
import platform.CoreGraphics.CGRectZero
import platform.UIKit.UIButton
import platform.UIKit.UIEvent

internal class InteropInteractionModeTest {
@Test
fun testUIButtonTapCooperativeDefault() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = {
TouchReactingView(
onTouchBegin = { beganCount++ },
onTouchEnd = { endedCount++ })
},
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(
UIKitInteropInteractionMode.Cooperative()
)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(0, beganCount)
assertEquals(0, endedCount)

delay(UIKitInteropInteractionMode.Cooperative.DefaultDelayMillis + 50L)

assertEquals(1, beganCount)
assertEquals(0, endedCount)

touch.up()

assertEquals(1, beganCount)
assertEquals(1, endedCount)
}

@Test
fun testUIButtonCooperativeTouchUpBeforeDelay() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(
interactionMode = UIKitInteropInteractionMode.Cooperative(delayMillis = 1000)
)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(0, beganCount)
assertEquals(0, endedCount)

delay(500)

touch.up()

assertEquals(1, beganCount)
assertEquals(1, endedCount)
}

@Test
fun testUIButtonCooperativeTouchUpAfterDelay() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(
interactionMode = UIKitInteropInteractionMode.Cooperative(delayMillis = 800)
)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(0, beganCount)
assertEquals(0, endedCount)

delay(500)

assertEquals(0, beganCount)
assertEquals(0, endedCount)

delay(350)

assertEquals(1, beganCount)
assertEquals(0, endedCount)

touch.up()

assertEquals(1, beganCount)
assertEquals(1, endedCount)
}

@Test
fun testUIButtonNonInteractive() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(null)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(0, beganCount)
assertEquals(0, endedCount)

touch.up()

assertEquals(0, beganCount)
assertEquals(0, endedCount)
}

@Test
fun testUIButtonTapNonCooperative() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(UIKitInteropInteractionMode.NonCooperative)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(1, beganCount)
assertEquals(0, endedCount)

touch.up()

assertEquals(1,beganCount)
assertEquals(1, endedCount)
}

@Test
fun testUIButtonLongTapNonCooperative() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(UIKitInteropInteractionMode.NonCooperative)
)
}

val touch = findNodeWithTag("Button")
.touchDown()

assertEquals(1, beganCount)
assertEquals(0, endedCount)

delay(500)

touch.up()

assertEquals(1,beganCount)
assertEquals(1, endedCount)
}

@Test
fun testUIButtonDoubleTapNonCooperative() = runUIKitInstrumentedTest {
var beganCount = 0
var endedCount = 0

setContent {
UIKitView(
factory = { TouchReactingView(onTouchBegin = { beganCount++ }, onTouchEnd = { endedCount++ }) },
modifier = Modifier.fillMaxWidth().height(50.dp).testTag("Button"),
properties = UIKitInteropProperties(UIKitInteropInteractionMode.NonCooperative)
)
}

findNodeWithTag("Button")
.doubleTap()

assertEquals(2, beganCount)
assertEquals(2, endedCount)
}
}

@OptIn(ExperimentalForeignApi::class)
private class TouchReactingView(
val onTouchBegin: () -> Unit,
val onTouchEnd: () -> Unit,
): UIButton(frame = CGRectZero.readValue()) {
override fun touchesBegan(touches: Set<*>, withEvent: UIEvent?) {
super.touchesBegan(touches, withEvent)
onTouchBegin()
}

override fun touchesEnded(touches: Set<*>, withEvent: UIEvent?) {
super.touchesEnded(touches, withEvent)
onTouchEnd()
}
}
Loading