diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index bffd9c7c72..9f9f8647a5 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -76,6 +76,13 @@ class ScreenStack( } override fun endViewTransition(view: View) { + require(view is ScreensCoordinatorLayout) { + "[RNScreens] Expected children of type: ${ScreensCoordinatorLayout::class.java.simpleName}" + } + if (view.blockFrameworkTransitionFinalization) { + return + } + super.endViewTransition(view) disappearingTransitioningChildren.remove(view) @@ -89,10 +96,17 @@ class ScreenStack( } } - fun onViewAppearTransitionEnd() { + internal fun onViewTransitionEnd(view: ScreensCoordinatorLayout) { if (!removalTransitionStarted) { dispatchOnFinishTransitioning() } + + if (view.needsTransitionFinalization) { + check(!view.blockFrameworkTransitionFinalization) { + "[RNScreens] Attempt to finalize transition on view that blocks the action!" + } + endViewTransition(view) + } } /** diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index dd82c6c75e..b9e6e2e908 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -148,19 +148,17 @@ class ScreenStackFragment : override fun onViewAnimationEnd() { super.onViewAnimationEnd() - - // Rely on guards inside the callee to detect whether this was indeed appear transition. - notifyViewAppearTransitionEnd() - - // Rely on guards inside the callee to detect whether this was indeed removal transition. - screen.endRemovalTransition() + notifyViewTransitionEnd() } - private fun notifyViewAppearTransitionEnd() { + private fun notifyViewTransitionEnd() { val screenStack = view?.parent if (screenStack is ScreenStack) { - screenStack.onViewAppearTransitionEnd() + screenStack.onViewTransitionEnd(coordinatorLayout) } + + // Rely on guards inside the callee to detect whether this was indeed removal transition. + screen.endRemovalTransition() } /** diff --git a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt index 8da0489f1b..b8fd8e6b19 100644 --- a/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt +++ b/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt @@ -128,14 +128,17 @@ class TabsHost( @Suppress("SENSELESS_COMPARISON") // layoutCallback can be null here since this method can be called in init if (!isLayoutEnqueued && layoutCallback != null) { isLayoutEnqueued = true - // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current - // looper loop instead of enqueueing the update in the next loop causing a one frame delay. - ReactChoreographer - .getInstance() - .postFrameCallback( - ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, - layoutCallback, - ) + post { + layoutCallback.doFrame(0) + } +// // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current +// // looper loop instead of enqueueing the update in the next loop causing a one frame delay. +// ReactChoreographer +// .getInstance() +// .postFrameCallback( +// ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, +// layoutCallback, +// ) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt b/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt index ff1409d9b2..487fa9d93b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt +++ b/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt @@ -23,6 +23,9 @@ internal class ScreensCoordinatorLayout( PointerEventsBoxNoneImpl(), ) + internal var blockFrameworkTransitionFinalization = false + internal var needsTransitionFinalization = false + override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets) private val animationListener: Animation.AnimationListener = @@ -32,7 +35,12 @@ internal class ScreensCoordinatorLayout( } override fun onAnimationEnd(animation: Animation) { + if (blockFrameworkTransitionFinalization) { + blockFrameworkTransitionFinalization = false + needsTransitionFinalization = true + } fragment.onViewAnimationEnd() + needsTransitionFinalization = false } override fun onAnimationRepeat(animation: Animation) {} @@ -50,6 +58,16 @@ internal class ScreensCoordinatorLayout( // We also add fakeAnimation to the set of animations, which sends the progress of animation val fakeAnimation = ScreensAnimation(fragment).apply { duration = animation.duration } + if (fragment.isRemoving) { + // This is a hack. + // We'll finalize the transition via our listener. + // We do it to prevent the framework from ending the transition prematurely, + // due to interaction between `FragmentAnim.EndViewTransitionAnimation` and + // `SafeAreaView` drawing blocking logic. For detailed description see: + // TODO LINK THE PR HERE + blockFrameworkTransitionFinalization = true + } + if (animation is AnimationSet && !fragment.isRemoving) { animation .apply {