diff --git a/compose/foundation/foundation-layout/src/uikitMain/kotlin/androidx/compose/foundation/layout/WindowInsets.uikit.kt b/compose/foundation/foundation-layout/src/uikitMain/kotlin/androidx/compose/foundation/layout/WindowInsets.uikit.kt index 87df800c26219..68c0402c979f8 100644 --- a/compose/foundation/foundation-layout/src/uikitMain/kotlin/androidx/compose/foundation/layout/WindowInsets.uikit.kt +++ b/compose/foundation/foundation-layout/src/uikitMain/kotlin/androidx/compose/foundation/layout/WindowInsets.uikit.kt @@ -74,8 +74,6 @@ actual val WindowInsets.Companion.displayCutout: WindowInsets /** * An insets type representing the window of an "input method", * for iOS IME representing the software keyboard. - * - * TODO: Animation doesn't work on iOS yet */ actual val WindowInsets.Companion.ime: WindowInsets @Composable diff --git a/compose/ui/ui/api/desktop/ui.api b/compose/ui/ui/api/desktop/ui.api index 794692bdaa40f..5c1f1bf8b24db 100644 --- a/compose/ui/ui/api/desktop/ui.api +++ b/compose/ui/ui/api/desktop/ui.api @@ -3853,8 +3853,8 @@ public final class androidx/compose/ui/window/DialogProperties { public static final field $stable I public fun (ZZZ)V public synthetic fun (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ZZZZJILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ZZZZJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZZZZJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZZZZJLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (ZZ[Ljava/lang/Void;Z)V public synthetic fun (ZZ[Ljava/lang/Void;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z @@ -3863,6 +3863,7 @@ public final class androidx/compose/ui/window/DialogProperties { public final fun getScrimColor-0d7_KjU ()J public final fun getUsePlatformDefaultWidth ()Z public final fun getUsePlatformInsets ()Z + public final fun getUseSoftwareKeyboardInset ()Z public fun hashCode ()I } diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformInsets.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformInsets.skiko.kt index f85d1216f70e0..e6f12e75c7e0e 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformInsets.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformInsets.skiko.kt @@ -67,6 +67,13 @@ class PlatformInsets( } } +internal fun PlatformInsets.union(insets: PlatformInsets) = PlatformInsets( + left = maxOf(left, insets.left), + top = maxOf(top, insets.top), + right = maxOf(right, insets.right), + bottom = maxOf(bottom, insets.bottom) +) + internal fun PlatformInsets.exclude(insets: PlatformInsets) = PlatformInsets( left = (left - insets.left).coerceAtLeast(0.dp), top = (top - insets.top).coerceAtLeast(0.dp), @@ -80,18 +87,32 @@ internal interface InsetsConfig { val safeInsets: PlatformInsets @Composable get + val ime: PlatformInsets + @Composable get + // Don't make it public, it should be implementation details for creating new root layout nodes. // TODO: Ensure encapsulation and proper control flow during refactoring [Owner]s @Composable - fun excludeSafeInsets(content: @Composable () -> Unit) + fun excludeInsets( + safeInsets: Boolean, + ime: Boolean, + content: @Composable () -> Unit + ) } internal object ZeroInsetsConfig : InsetsConfig { override val safeInsets: PlatformInsets @Composable get() = PlatformInsets.Zero + override val ime: PlatformInsets + @Composable get() = PlatformInsets.Zero + @Composable - override fun excludeSafeInsets(content: @Composable () -> Unit) { + override fun excludeInsets( + safeInsets: Boolean, + ime: Boolean, + content: @Composable () -> Unit + ) { content() } } @@ -103,6 +124,6 @@ internal object ZeroInsetsConfig : InsetsConfig { * different on each platform. * * TODO: Stabilize and make the window paddings in the foundation-layout module depend on it. - * There is a plan to potentially move this variable into the [Platform] interface. + * There is a plan to potentially move this variable into the [PlatformContext] interface. */ internal expect var PlatformInsetsConfig: InsetsConfig diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt index 68becd983b8b5..cb0c238f782a5 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt @@ -29,11 +29,10 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.layout.Layout -import androidx.compose.ui.platform.InsetsConfig import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.platform.PlatformInsets import androidx.compose.ui.platform.PlatformInsetsConfig -import androidx.compose.ui.platform.ZeroInsetsConfig +import androidx.compose.ui.platform.union import androidx.compose.ui.scene.ComposeSceneLayer import androidx.compose.ui.scene.rememberComposeSceneLayer import androidx.compose.ui.semantics.dialog @@ -41,6 +40,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.center +import androidx.compose.ui.unit.dp /** * The default scrim opacity. @@ -58,8 +58,10 @@ private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity) * dialog's bounds. If true, clicking outside the dialog will call onDismissRequest. * @property usePlatformDefaultWidth Whether the width of the dialog's content should be limited to * the platform default, which is smaller than the screen width. - * @property usePlatformInsets Whether the width of the popup's content should be limited by + * @property usePlatformInsets Whether the size of the dialog's content should be limited by * platform insets. + * @property useSoftwareKeyboardInset Whether the size of the dialog's content should be limited by + * software keyboard inset. * @property scrimColor Color of background fill. */ @Immutable @@ -68,6 +70,7 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor( actual val dismissOnClickOutside: Boolean = true, actual val usePlatformDefaultWidth: Boolean = true, val usePlatformInsets: Boolean = true, + val useSoftwareKeyboardInset: Boolean = true, val scrimColor: Color = DefaultScrimColor, ) { // Constructor with all non-experimental arguments. @@ -80,6 +83,7 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor( dismissOnClickOutside = dismissOnClickOutside, usePlatformDefaultWidth = usePlatformDefaultWidth, usePlatformInsets = true, + useSoftwareKeyboardInset = true, scrimColor = DefaultScrimColor, ) @@ -102,6 +106,7 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor( dismissOnClickOutside = dismissOnClickOutside, usePlatformDefaultWidth = usePlatformDefaultWidth, usePlatformInsets = true, + useSoftwareKeyboardInset = true, scrimColor = DefaultScrimColor, ) @@ -113,6 +118,7 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor( if (dismissOnClickOutside != other.dismissOnClickOutside) return false if (usePlatformDefaultWidth != other.usePlatformDefaultWidth) return false if (usePlatformInsets != other.usePlatformInsets) return false + if (useSoftwareKeyboardInset != other.useSoftwareKeyboardInset) return false if (scrimColor != other.scrimColor) return false return true @@ -123,6 +129,7 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor( result = 31 * result + dismissOnClickOutside.hashCode() result = 31 * result + usePlatformDefaultWidth.hashCode() result = 31 * result + usePlatformInsets.hashCode() + result = 31 * result + useSoftwareKeyboardInset.hashCode() result = 31 * result + scrimColor.hashCode() return result } @@ -173,7 +180,7 @@ private fun DialogLayout( onOutsidePointerEvent: ((eventType: PointerEventType) -> Unit)? = null, content: @Composable () -> Unit ) { - val platformInsets = properties.insetsConfig.safeInsets + val platformInsets = properties.platformInsets val layer = rememberComposeSceneLayer( focusable = true ) @@ -188,7 +195,10 @@ private fun DialogLayout( containerSize = containerSize, platformInsets = platformInsets ) - properties.insetsConfig.excludeSafeInsets { + PlatformInsetsConfig.excludeInsets( + safeInsets = properties.usePlatformInsets, + ime = properties.useSoftwareKeyboardInset, + ) { Layout( content = content, modifier = modifier, @@ -198,6 +208,21 @@ private fun DialogLayout( } } +private val DialogProperties.platformInsets: PlatformInsets + @Composable get() { + val safeInsets = if (usePlatformInsets) { + PlatformInsetsConfig.safeInsets + } else { + PlatformInsets.Zero + } + val ime = if (useSoftwareKeyboardInset) { + PlatformInsetsConfig.ime + } else { + PlatformInsets.Zero + } + return safeInsets.union(ime) + } + @Composable private fun rememberLayerContent(layer: ComposeSceneLayer, content: @Composable () -> Unit) { remember(layer, content) { @@ -205,9 +230,6 @@ private fun rememberLayerContent(layer: ComposeSceneLayer, content: @Composable } } -private val DialogProperties.insetsConfig: InsetsConfig - get() = if (usePlatformInsets) PlatformInsetsConfig else ZeroInsetsConfig - @Composable private fun rememberDialogMeasurePolicy( layer: ComposeSceneLayer, diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Popup.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Popup.skiko.kt index 96e15200004c6..8a1b50ebbd293 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Popup.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Popup.skiko.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.platform.PlatformInsets import androidx.compose.ui.platform.PlatformInsetsConfig import androidx.compose.ui.platform.ZeroInsetsConfig +import androidx.compose.ui.platform.union import androidx.compose.ui.scene.ComposeSceneLayer import androidx.compose.ui.scene.rememberComposeSceneLayer import androidx.compose.ui.semantics.popup @@ -433,7 +434,7 @@ private fun PopupLayout( onOutsidePointerEvent: ((eventType: PointerEventType) -> Unit)? = null, content: @Composable () -> Unit ) { - val platformInsets = properties.insetsConfig.safeInsets + val platformInsets = properties.platformInsets var layoutParentBoundsInWindow: IntRect? by remember { mutableStateOf(null) } EmptyLayout(Modifier.parentBoundsInWindow { layoutParentBoundsInWindow = it }) val layer = rememberComposeSceneLayer( @@ -454,7 +455,10 @@ private fun PopupLayout( layoutDirection = layoutDirection, parentBoundsInWindow = parentBoundsInWindow ) - properties.insetsConfig.excludeSafeInsets { + PlatformInsetsConfig.excludeInsets( + safeInsets = properties.usePlatformInsets, + ime = false, + ) { Layout( content = content, modifier = modifier, @@ -464,6 +468,13 @@ private fun PopupLayout( } } +private val PopupProperties.platformInsets: PlatformInsets + @Composable get() = if (usePlatformInsets) { + PlatformInsetsConfig.safeInsets + } else { + PlatformInsets.Zero + } + @Composable private fun rememberLayerContent(layer: ComposeSceneLayer, content: @Composable () -> Unit) { remember(layer, content) { @@ -480,9 +491,6 @@ private fun EmptyLayout(modifier: Modifier = Modifier) = Layout( } ) -private val PopupProperties.insetsConfig: InsetsConfig - get() = if (usePlatformInsets) PlatformInsetsConfig else ZeroInsetsConfig - private fun Modifier.parentBoundsInWindow( onBoundsChanged: (IntRect) -> Unit ) = this.onGloballyPositioned { childCoordinates -> diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalUIKitInteropContext.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalUIKitInteropContext.kt index 91ca720df8317..ee8d380650b6f 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalUIKitInteropContext.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalUIKitInteropContext.kt @@ -23,7 +23,7 @@ internal enum class UIKitInteropState { BEGAN, UNCHANGED, ENDED } -internal enum class UIKitInteropViewHierarchyChange { +internal enum class UIKitInteropEvent { VIEW_ADDED, VIEW_REMOVED } @@ -38,7 +38,7 @@ internal interface UIKitInteropTransaction { val state: UIKitInteropState companion object { - val empty = object : UIKitInteropTransaction { + val empty: UIKitInteropTransaction = object : UIKitInteropTransaction { override val actions: List get() = emptyList() @@ -77,7 +77,7 @@ private class UIKitInteropMutableTransaction: UIKitInteropTransaction { /** * Class which can be used to add actions related to UIKit objects to be executed in sync with compose rendering, - * Addding deferred actions is threadsafe, but they will be executed in the order of their submission, and on the main thread. + * Adding deferred actions is thread-safe, but they will be executed in the order of their submission, and on the main thread. */ internal class UIKitInteropContext( val requestRedraw: () -> Unit @@ -86,9 +86,9 @@ internal class UIKitInteropContext( private var transaction = UIKitInteropMutableTransaction() /** - * Number of views, created by interop API and present in current view hierarchy + * Number of views whose changes are being synchronized with Compose rendering */ - private var viewsCount = 0 + private var synchronizedViewsCount = 0 set(value) { require(value >= 0) @@ -98,22 +98,22 @@ internal class UIKitInteropContext( /** * Add lambda to a list of commands which will be executed later in the same CATransaction, when the next rendered Compose frame is presented */ - fun deferAction(hierarchyChange: UIKitInteropViewHierarchyChange? = null, action: () -> Unit) { + fun deferAction(event: UIKitInteropEvent? = null, action: () -> Unit) { requestRedraw() lock.doLocked { - if (hierarchyChange == UIKitInteropViewHierarchyChange.VIEW_ADDED) { - if (viewsCount == 0) { + if (event == UIKitInteropEvent.VIEW_ADDED) { + if (synchronizedViewsCount == 0) { transaction.state = UIKitInteropState.BEGAN } - viewsCount += 1 + synchronizedViewsCount += 1 } transaction.actions.add(action) - if (hierarchyChange == UIKitInteropViewHierarchyChange.VIEW_REMOVED) { - viewsCount -= 1 - if (viewsCount == 0) { + if (event == UIKitInteropEvent.VIEW_REMOVED) { + synchronizedViewsCount -= 1 + if (synchronizedViewsCount == 0) { transaction.state = UIKitInteropState.ENDED } } diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt index 72379907ee8a6..c5601e76dbe74 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt @@ -132,12 +132,12 @@ fun UIKitView( interopContext.deferAction(action = it) } - interopContext.deferAction(UIKitInteropViewHierarchyChange.VIEW_ADDED) { + interopContext.deferAction(UIKitInteropEvent.VIEW_ADDED) { embeddedInteropComponent.addToHierarchy() } onDispose { - interopContext.deferAction(UIKitInteropViewHierarchyChange.VIEW_REMOVED) { + interopContext.deferAction(UIKitInteropEvent.VIEW_REMOVED) { embeddedInteropComponent.removeFromHierarchy() } } @@ -231,12 +231,12 @@ fun UIKitViewController( interopContext.deferAction(action = it) } - interopContext.deferAction(UIKitInteropViewHierarchyChange.VIEW_ADDED) { + interopContext.deferAction(UIKitInteropEvent.VIEW_ADDED) { embeddedInteropComponent.addToHierarchy() } onDispose { - interopContext.deferAction(UIKitInteropViewHierarchyChange.VIEW_REMOVED) { + interopContext.deferAction(UIKitInteropEvent.VIEW_REMOVED) { embeddedInteropComponent.removeFromHierarchy() } } diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/PlatformInsets.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/PlatformInsets.uikit.kt index 0455e72027d6d..803e58eb0e4c5 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/PlatformInsets.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/PlatformInsets.uikit.kt @@ -20,6 +20,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.uikit.LocalKeyboardOverlapHeight +import androidx.compose.ui.unit.dp /** * Composition local for SafeArea of ComposeUIViewController @@ -38,13 +40,22 @@ private object SafeAreaInsetsConfig : InsetsConfig { override val safeInsets: PlatformInsets @Composable get() = LocalSafeArea.current + override val ime: PlatformInsets + @Composable get() = PlatformInsets(bottom = LocalKeyboardOverlapHeight.current.dp) + @Composable - override fun excludeSafeInsets(content: @Composable () -> Unit) { + override fun excludeInsets( + safeInsets: Boolean, + ime: Boolean, + content: @Composable () -> Unit + ) { val safeArea = LocalSafeArea.current val layoutMargins = LocalLayoutMargins.current + val keyboardOverlapHeight = LocalKeyboardOverlapHeight.current CompositionLocalProvider( - LocalSafeArea provides PlatformInsets(), - LocalLayoutMargins provides layoutMargins.exclude(safeArea), + LocalSafeArea provides if (safeInsets) PlatformInsets() else safeArea, + LocalLayoutMargins provides if (safeInsets) layoutMargins.exclude(safeArea) else layoutMargins, + LocalKeyboardOverlapHeight provides if (ime) 0f else keyboardOverlapHeight, content = content ) } diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt index 4c9bdcf70bc7f..f6415cf9c6c7d 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt @@ -264,8 +264,9 @@ internal class ComposeSceneMediator( keyboardOverlapHeightState = keyboardOverlapHeightState, viewProvider = { container }, densityProvider = { container.systemDensity }, - composeSceneMediatorProvider = { this }, focusManager = focusManager, + getMediatorViewHeight = { getViewHeight() }, + interopContext = interopContext ) } @@ -429,7 +430,7 @@ internal class ComposeSceneMediator( fun onComposeSceneInvalidate() = renderingView.needRedraw() - fun setLayout(value: SceneLayout) { + fun setLayout(value: SceneLayout) = interopContext.deferAction { _layout = value when (value) { SceneLayout.UseConstraintsToFillContainer -> { @@ -577,7 +578,10 @@ internal class ComposeSceneMediator( ) } - fun getViewHeight(): Double = renderingView.frame.useContents { + /** + * Returns the height of the rendering view in DPs. + */ + private fun getViewHeight(): Double = renderingView.frame.useContents { size.height } diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/KeyboardVisibilityListener.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/KeyboardVisibilityListener.uikit.kt index 87b44e79b4c39..075405f5348f6 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/KeyboardVisibilityListener.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/KeyboardVisibilityListener.uikit.kt @@ -17,8 +17,9 @@ package androidx.compose.ui.window import androidx.compose.runtime.MutableState +import androidx.compose.ui.interop.UIKitInteropContext +import androidx.compose.ui.interop.UIKitInteropEvent import androidx.compose.ui.scene.ComposeSceneFocusManager -import androidx.compose.ui.scene.ComposeSceneMediator import androidx.compose.ui.uikit.ComposeUIViewControllerConfiguration import androidx.compose.ui.uikit.OnFocusBehavior import androidx.compose.ui.unit.Density @@ -54,8 +55,9 @@ internal class KeyboardVisibilityListenerImpl( private val keyboardOverlapHeightState: MutableState, private val viewProvider: () -> UIView, private val densityProvider: () -> Density, - private val composeSceneMediatorProvider: () -> ComposeSceneMediator, + private val getMediatorViewHeight: () -> Double, private val focusManager: ComposeSceneFocusManager, + private val interopContext: UIKitInteropContext, ) : KeyboardVisibilityListener { val view get() = viewProvider() @@ -66,12 +68,13 @@ internal class KeyboardVisibilityListenerImpl( hidden = true } } + + // TODO: refactor and integrate with MetalRedrawer displayLink private var keyboardAnimationListener: CADisplayLink? = null override fun keyboardWillShow(arg: NSNotification) { animateKeyboard(arg, true) - val mediator = composeSceneMediatorProvider() val userInfo = arg.userInfo ?: return val keyboardInfo = userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue val keyboardHeight = keyboardInfo.CGRectValue().useContents { size.height } @@ -80,7 +83,7 @@ internal class KeyboardVisibilityListenerImpl( if (focusedRect != null) { updateViewBounds( - offsetY = calcFocusedLiftingY(mediator, focusedRect, keyboardHeight) + offsetY = calcFocusedLiftingY(focusedRect, keyboardHeight) ) } } @@ -144,6 +147,7 @@ internal class KeyboardVisibilityListenerImpl( }, selector = sel_registerName("animationDidUpdate") ).apply { + view.window?.screen?.maximumFramesPerSecond?.let { preferredFramesPerSecond = it } addToRunLoop(NSRunLoop.mainRunLoop(), NSDefaultRunLoopMode) } @@ -155,30 +159,34 @@ internal class KeyboardVisibilityListenerImpl( } else { 0.0 } - UIView.animateWithDuration( - duration = duration, - animations = { - //set final destination for animation - keyboardAnimationView.setFrame(CGRectMake(0.0, toValue, 0.0, 0.0)) - }, - completion = { isFinished -> - if (isFinished) { - keyboardAnimationListener?.invalidate() - keyboardAnimationListener = null - keyboardAnimationView.removeFromSuperview() - } else { - //animation was canceled by other animation + + interopContext.deferAction(UIKitInteropEvent.VIEW_ADDED) { + UIView.animateWithDuration( + duration = duration, + animations = { + //set final destination for animation + keyboardAnimationView.setFrame(CGRectMake(0.0, toValue, 0.0, 0.0)) + }, + completion = { isFinished -> + interopContext.deferAction(UIKitInteropEvent.VIEW_REMOVED) { + if (isFinished) { + keyboardAnimationListener?.invalidate() + keyboardAnimationListener = null + keyboardAnimationView.removeFromSuperview() + } else { + //animation was canceled by other animation + } + } } - } - ) + ) + } } private fun calcFocusedLiftingY( - composeSceneMediator: ComposeSceneMediator, focusedRect: DpRect, keyboardHeight: Double ): Double { - val viewHeight = composeSceneMediator.getViewHeight() + val viewHeight = getMediatorViewHeight() val hiddenPartOfFocusedElement: Double = keyboardHeight - viewHeight + focusedRect.bottom.value return if (hiddenPartOfFocusedElement > 0) {