From 43a9dfdb8f3731c91f98bbd42ee4964519ba1550 Mon Sep 17 00:00:00 2001 From: tboba Date: Tue, 23 Apr 2024 09:59:53 +0200 Subject: [PATCH 1/8] Add first prototype of restoring Android animation, work on events --- TestsExample/App.js | 2 +- TestsExample/src/Test1072.tsx | 2 +- .../java/com/swmansion/rnscreens/Screen.kt | 11 ++++++ .../com/swmansion/rnscreens/ScreenFragment.kt | 30 +++++++++++++++ .../com/swmansion/rnscreens/ScreenStack.kt | 17 +++++---- .../rnscreens/ScreenStackFragment.kt | 12 +++++- .../res/base/anim/rns_default_enter_in.xml | 18 --------- .../res/base/anim/rns_default_enter_out.xml | 19 ---------- .../res/base/anim/rns_default_exit_in.xml | 17 --------- .../res/base/anim/rns_default_exit_out.xml | 18 --------- .../src/main/res/base/anim/rns_fade_in.xml | 7 ---- .../src/main/res/base/anim/rns_fade_out.xml | 7 ---- .../res/base/anim/rns_no_animation_20.xml | 6 --- .../res/v33/anim-v33/rns_default_enter_in.xml | 37 ------------------ .../v33/anim-v33/rns_default_enter_out.xml | 38 ------------------- .../res/v33/anim-v33/rns_default_exit_in.xml | 38 ------------------- .../res/v33/anim-v33/rns_default_exit_out.xml | 38 ------------------- react-navigation | 2 +- src/native-stack/views/NativeStackView.tsx | 4 ++ 19 files changed, 69 insertions(+), 254 deletions(-) delete mode 100644 android/src/main/res/base/anim/rns_default_enter_in.xml delete mode 100644 android/src/main/res/base/anim/rns_default_enter_out.xml delete mode 100644 android/src/main/res/base/anim/rns_default_exit_in.xml delete mode 100644 android/src/main/res/base/anim/rns_default_exit_out.xml delete mode 100644 android/src/main/res/base/anim/rns_fade_in.xml delete mode 100644 android/src/main/res/base/anim/rns_fade_out.xml delete mode 100644 android/src/main/res/base/anim/rns_no_animation_20.xml delete mode 100644 android/src/main/res/v33/anim-v33/rns_default_enter_in.xml delete mode 100644 android/src/main/res/v33/anim-v33/rns_default_enter_out.xml delete mode 100644 android/src/main/res/v33/anim-v33/rns_default_exit_in.xml delete mode 100644 android/src/main/res/v33/anim-v33/rns_default_exit_out.xml diff --git a/TestsExample/App.js b/TestsExample/App.js index b34e9d99f1..e5c85f0b60 100644 --- a/TestsExample/App.js +++ b/TestsExample/App.js @@ -100,5 +100,5 @@ import Test2069 from './src/Test2069'; enableFreeze(true); export default function App() { - return ; + return ; } diff --git a/TestsExample/src/Test1072.tsx b/TestsExample/src/Test1072.tsx index 7535c82dd8..c11a8d1ebe 100644 --- a/TestsExample/src/Test1072.tsx +++ b/TestsExample/src/Test1072.tsx @@ -46,7 +46,7 @@ export default function App() { diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index b31e3788e1..5eb681f534 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -6,6 +6,7 @@ import android.graphics.Paint import android.os.Parcelable import android.util.SparseArray import android.util.TypedValue +import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.webkit.WebView @@ -50,6 +51,16 @@ class Screen(context: ReactContext?) : FabricEnabledViewGroup(context) { layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION) } + override fun startViewTransition(view: View?) { + super.startViewTransition(view) + fragmentWrapper?.onViewAnimationStart() + } + + override fun endViewTransition(view: View?) { + super.endViewTransition(view) + fragmentWrapper?.onViewAnimationEnd() + } + override fun dispatchSaveInstanceState(container: SparseArray) { // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize // view's states. The side effect of restoring is that TextInput components would trigger diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt index 3915dd6c1e..1332392e09 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -10,6 +10,9 @@ import android.view.ViewGroup import android.view.ViewParent import android.widget.FrameLayout import androidx.fragment.app.Fragment +import androidx.transition.Transition +import androidx.transition.Transition.TransitionListener +import androidx.transition.TransitionSet import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.UIManagerHelper @@ -66,6 +69,33 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { screen = screenView } + override fun setEnterTransition(transition: Any?) { + if (transition != null && transition is TransitionSet) { + transition.addListener(object: TransitionListener { + override fun onTransitionStart(transition: Transition) { + TODO("Not yet implemented") + } + + override fun onTransitionEnd(transition: Transition) { + TODO("Not yet implemented") + } + + override fun onTransitionCancel(transition: Transition) { + TODO("Not yet implemented") + } + + override fun onTransitionPause(transition: Transition) { + TODO("Not yet implemented") + } + + override fun onTransitionResume(transition: Transition) { + TODO("Not yet implemented") + } + + }) + } + } + override fun onResume() { super.onResume() if (shouldUpdateOnResume) { diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 458465ce9f..940d9d043a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Canvas import android.os.Build import android.view.View +import androidx.fragment.app.FragmentTransaction import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.rnscreens.Screen.StackAnimation @@ -50,11 +51,13 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { override fun startViewTransition(view: View) { super.startViewTransition(view) + topScreenWrapper?.onViewAnimationStart() removalTransitionStarted = true } override fun endViewTransition(view: View) { super.endViewTransition(view) + topScreenWrapper?.onViewAnimationEnd() if (removalTransitionStarted) { removalTransitionStarted = false dispatchOnFinishTransitioning() @@ -139,9 +142,9 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { if (stackAnimation != null) { if (shouldUseOpenAnimation) { when (stackAnimation) { - StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_enter_in, R.anim.rns_default_enter_out) - StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) - StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) + StackAnimation.DEFAULT -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + StackAnimation.NONE -> it.setTransition(FragmentTransaction.TRANSIT_NONE) + StackAnimation.FADE -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( @@ -152,9 +155,9 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { } } else { when (stackAnimation) { - StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_exit_in, R.anim.rns_default_exit_out) - StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) - StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) + StackAnimation.DEFAULT -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) + StackAnimation.NONE -> it.setTransition(FragmentTransaction.TRANSIT_NONE) + StackAnimation.FADE -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( @@ -339,7 +342,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { // On Android sdk 33 and above the animation is different and requires draw reordering. // For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet. // Hence, we're comparing numerical version here. - Build.VERSION.SDK_INT >= 33 || +// Build.VERSION.SDK_INT >= 33 || fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.IOS diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 48ff68440c..d46d84df82 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -212,7 +212,7 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { container.dismiss(this) } - private class ScreensCoordinatorLayout( + class ScreensCoordinatorLayout( context: Context, private val mFragment: ScreenFragment ) : CoordinatorLayout(context) { @@ -229,6 +229,16 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { override fun onAnimationRepeat(animation: Animation) {} } + override fun startViewTransition(view: View?) { + super.startViewTransition(view) + mFragment.onViewAnimationStart() + } + + override fun endViewTransition(view: View?) { + super.endViewTransition(view) + mFragment.onViewAnimationEnd() + } + override fun startAnimation(animation: Animation) { // For some reason View##onAnimationEnd doesn't get called for // exit transitions so we explicitly attach animation listener. diff --git a/android/src/main/res/base/anim/rns_default_enter_in.xml b/android/src/main/res/base/anim/rns_default_enter_in.xml deleted file mode 100644 index 4398c7efc0..0000000000 --- a/android/src/main/res/base/anim/rns_default_enter_in.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/android/src/main/res/base/anim/rns_default_enter_out.xml b/android/src/main/res/base/anim/rns_default_enter_out.xml deleted file mode 100644 index 84c91759fe..0000000000 --- a/android/src/main/res/base/anim/rns_default_enter_out.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/android/src/main/res/base/anim/rns_default_exit_in.xml b/android/src/main/res/base/anim/rns_default_exit_in.xml deleted file mode 100644 index 6d6fa02acd..0000000000 --- a/android/src/main/res/base/anim/rns_default_exit_in.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - diff --git a/android/src/main/res/base/anim/rns_default_exit_out.xml b/android/src/main/res/base/anim/rns_default_exit_out.xml deleted file mode 100644 index b20a184f8b..0000000000 --- a/android/src/main/res/base/anim/rns_default_exit_out.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/android/src/main/res/base/anim/rns_fade_in.xml b/android/src/main/res/base/anim/rns_fade_in.xml deleted file mode 100644 index c78ea619df..0000000000 --- a/android/src/main/res/base/anim/rns_fade_in.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/android/src/main/res/base/anim/rns_fade_out.xml b/android/src/main/res/base/anim/rns_fade_out.xml deleted file mode 100644 index 334e63f34e..0000000000 --- a/android/src/main/res/base/anim/rns_fade_out.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/android/src/main/res/base/anim/rns_no_animation_20.xml b/android/src/main/res/base/anim/rns_no_animation_20.xml deleted file mode 100644 index 5cc0d2385b..0000000000 --- a/android/src/main/res/base/anim/rns_no_animation_20.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml b/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml deleted file mode 100644 index 1767203511..0000000000 --- a/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - diff --git a/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml b/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml deleted file mode 100644 index e7dd72bba5..0000000000 --- a/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - diff --git a/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml b/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml deleted file mode 100644 index 949ebb776d..0000000000 --- a/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - diff --git a/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml b/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml deleted file mode 100644 index ba4d84d6cd..0000000000 --- a/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - diff --git a/react-navigation b/react-navigation index 523fa4f3af..d0abdee67f 160000 --- a/react-navigation +++ b/react-navigation @@ -1 +1 @@ -Subproject commit 523fa4f3afb7c428a8370bfd83933c0163224e66 +Subproject commit d0abdee67f5db8cf39112af535846ffededfb21d diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 6d3e82f818..798dd5ae1b 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -324,6 +324,7 @@ const RouteView = ({ data: { closing: false }, target: route.key, }); + console.error('Will appear'); }} onWillDisappear={() => { navigation.emit({ @@ -331,12 +332,14 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); + console.error('Will disappear'); }} onAppear={() => { navigation.emit({ type: 'appear', target: route.key, }); + console.error('Appear'); navigation.emit({ type: 'transitionEnd', data: { closing: false }, @@ -349,6 +352,7 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); + console.error('Disappear'); }} onHeaderHeightChange={e => { const headerHeight = e.nativeEvent.headerHeight; From 988c8c614413f4496b53e9085e4d0fcfb77f52aa Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 24 Apr 2024 15:10:58 +0200 Subject: [PATCH 2/8] Get ready prototype (but with wrong event direction) --- TestsExample/src/Test1072.tsx | 9 +++- .../java/com/swmansion/rnscreens/Screen.kt | 2 - .../com/swmansion/rnscreens/ScreenFragment.kt | 38 +++-------------- .../com/swmansion/rnscreens/ScreenStack.kt | 11 ++--- .../rnscreens/ScreenStackFragment.kt | 42 ++++++++++++++----- .../rnscreens/ScreenStackViewManager.kt | 1 + src/native-stack/views/NativeStackView.tsx | 8 ++-- 7 files changed, 56 insertions(+), 55 deletions(-) diff --git a/TestsExample/src/Test1072.tsx b/TestsExample/src/Test1072.tsx index c11a8d1ebe..cc4b969279 100644 --- a/TestsExample/src/Test1072.tsx +++ b/TestsExample/src/Test1072.tsx @@ -28,13 +28,17 @@ function First({ ); } -function Second() { +function Second({ + navigation, +}: { + navigation: NativeStackNavigationProp; +}) { return ( Use swipe back gesture to go back (iOS only) - + navigation.navigate('Third')} /> ); } @@ -51,6 +55,7 @@ export default function App() { }}> + diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 5eb681f534..244fe640b4 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -53,12 +53,10 @@ class Screen(context: ReactContext?) : FabricEnabledViewGroup(context) { override fun startViewTransition(view: View?) { super.startViewTransition(view) - fragmentWrapper?.onViewAnimationStart() } override fun endViewTransition(view: View?) { super.endViewTransition(view) - fragmentWrapper?.onViewAnimationEnd() } override fun dispatchSaveInstanceState(container: SparseArray) { diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt index 1332392e09..a9312f10ac 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewParent +import android.view.animation.Animation import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.transition.Transition @@ -69,33 +70,6 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { screen = screenView } - override fun setEnterTransition(transition: Any?) { - if (transition != null && transition is TransitionSet) { - transition.addListener(object: TransitionListener { - override fun onTransitionStart(transition: Transition) { - TODO("Not yet implemented") - } - - override fun onTransitionEnd(transition: Transition) { - TODO("Not yet implemented") - } - - override fun onTransitionCancel(transition: Transition) { - TODO("Not yet implemented") - } - - override fun onTransitionPause(transition: Transition) { - TODO("Not yet implemented") - } - - override fun onTransitionResume(transition: Transition) { - TODO("Not yet implemented") - } - - }) - } - } - override fun onResume() { super.onResume() if (shouldUpdateOnResume) { @@ -200,22 +174,22 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { } } - private fun dispatchOnWillAppear() { + fun dispatchOnWillAppear() { dispatchLifecycleEvent(ScreenLifecycleEvent.WILL_APPEAR, this) dispatchTransitionProgressEvent(0.0f, false) } - private fun dispatchOnAppear() { + fun dispatchOnAppear() { dispatchLifecycleEvent(ScreenLifecycleEvent.DID_APPEAR, this) dispatchTransitionProgressEvent(1.0f, false) } - private fun dispatchOnWillDisappear() { + fun dispatchOnWillDisappear() { dispatchLifecycleEvent(ScreenLifecycleEvent.WILL_DISAPPEAR, this) dispatchTransitionProgressEvent(0.0f, true) } - private fun dispatchOnDisappear() { + fun dispatchOnDisappear() { dispatchLifecycleEvent(ScreenLifecycleEvent.DID_DISAPPEAR, this) dispatchTransitionProgressEvent(1.0f, true) } @@ -302,7 +276,7 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root // view. We override an appropriate method of the StackFragment's // root view in order to achieve this. - if (isResumed) { + if (isResumed || screen.container?.topScreen === screen) { // Android dispatches the animation start event for the fragment that is being added first // however we want the one being dismissed first to match iOS. It also makes more sense from // a navigation point of view to have the disappear event first. diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 940d9d043a..7dea8cb020 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -2,7 +2,6 @@ package com.swmansion.rnscreens import android.content.Context import android.graphics.Canvas -import android.os.Build import android.view.View import androidx.fragment.app.FragmentTransaction import com.facebook.react.bridge.ReactContext @@ -10,8 +9,6 @@ import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.rnscreens.Screen.StackAnimation import com.swmansion.rnscreens.events.StackFinishTransitioningEvent import java.util.Collections -import kotlin.collections.ArrayList -import kotlin.collections.HashSet class ScreenStack(context: Context?) : ScreenContainer(context) { private val stack = ArrayList() @@ -19,6 +16,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { private val drawingOpPool: MutableList = ArrayList() private var drawingOps: MutableList = ArrayList() private var topScreenWrapper: ScreenStackFragmentWrapper? = null + private var previousTopScreenWrapper: ScreenStackFragmentWrapper? = null private var removalTransitionStarted = false private var isDetachingCurrentScreen = false private var reverseLastTwoChildren = false @@ -51,15 +49,17 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { override fun startViewTransition(view: View) { super.startViewTransition(view) - topScreenWrapper?.onViewAnimationStart() removalTransitionStarted = true + previousTopScreenWrapper?.onViewAnimationStart() + topScreenWrapper?.onViewAnimationStart() } override fun endViewTransition(view: View) { super.endViewTransition(view) - topScreenWrapper?.onViewAnimationEnd() if (removalTransitionStarted) { removalTransitionStarted = false + previousTopScreenWrapper?.onViewAnimationEnd() + topScreenWrapper?.onViewAnimationEnd() dispatchOnFinishTransitioning() } } @@ -221,6 +221,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { } else if (newTop != null && !newTop.fragment.isAdded) { it.add(id, newTop.fragment) } + previousTopScreenWrapper = topScreenWrapper topScreenWrapper = newTop as? ScreenStackFragmentWrapper stack.clear() stack.addAll(screenWrappers.map { it as ScreenStackFragmentWrapper }) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index d46d84df82..f57336403f 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -1,9 +1,11 @@ package com.swmansion.rnscreens +import android.animation.Animator import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -16,6 +18,7 @@ import android.view.animation.Transformation import android.widget.LinearLayout import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.PixelUtil import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior @@ -86,6 +89,35 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { notifyViewAppearTransitionEnd() } + override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { + // this means that the fragment will appear with a custom transition, in the case + // of animation: 'none', onViewAnimationStart and onViewAnimationEnd + // won't be called and we need to notify stack directly from here. + // When using the Toolbar back button this is called an extra time with transit = 0 but in + // this case we don't want to notify. The way I found to detect is case is check isHidden. + if (transit == 0 && !isHidden +// && screen.stackAnimation === Screen.StackAnimation.NONE + ) { + if (enter) { + // Android dispatches the animation start event for the fragment that is being added first + // however we want the one being dismissed first to match iOS. It also makes more sense + // from a navigation point of view to have the disappear event first. + // Since there are no explicit relationships between the fragment being added / removed + // the practical way to fix this is delaying dispatching the appear events at the end of + // the frame. + UiThreadUtil.runOnUiThread { + dispatchOnWillAppear() + dispatchOnAppear() + } + } else { + dispatchOnWillDisappear() + dispatchOnDisappear() + notifyViewAppearTransitionEnd() + } + } + return null + } + private fun notifyViewAppearTransitionEnd() { val screenStack = view?.parent if (screenStack is ScreenStack) { @@ -229,16 +261,6 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { override fun onAnimationRepeat(animation: Animation) {} } - override fun startViewTransition(view: View?) { - super.startViewTransition(view) - mFragment.onViewAnimationStart() - } - - override fun endViewTransition(view: View?) { - super.endViewTransition(view) - mFragment.onViewAnimationEnd() - } - override fun startAnimation(animation: Animation) { // For some reason View##onAnimationEnd doesn't get called for // exit transitions so we explicitly attach animation listener. diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt index cbbf0d5209..d3b605bfc1 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt @@ -26,6 +26,7 @@ class ScreenStackViewManager : ViewGroupManager(), RNSScreenStackMa override fun addView(parent: ScreenStack, child: View, index: Int) { require(child is Screen) { "Attempt attach child that is not of type RNScreen" } + prepareOutTransition(child) parent.addScreen(child, index) } diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index 798dd5ae1b..d60896eeb3 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -324,7 +324,7 @@ const RouteView = ({ data: { closing: false }, target: route.key, }); - console.error('Will appear'); + console.error(`SCREEN ${index}:Will appear`); }} onWillDisappear={() => { navigation.emit({ @@ -332,14 +332,14 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); - console.error('Will disappear'); + console.error(`SCREEN ${index}:Will disappear`); }} onAppear={() => { navigation.emit({ type: 'appear', target: route.key, }); - console.error('Appear'); + console.error(`SCREEN ${index}:Appear`); navigation.emit({ type: 'transitionEnd', data: { closing: false }, @@ -352,7 +352,7 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); - console.error('Disappear'); + console.error(`SCREEN ${index}:Disappear`); }} onHeaderHeightChange={e => { const headerHeight = e.nativeEvent.headerHeight; From 88c29869269f955d5c09bf0ff6b8121af0ed53d0 Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 24 Apr 2024 16:06:48 +0200 Subject: [PATCH 3/8] Add running events with correct order --- .../com/swmansion/rnscreens/ScreenStack.kt | 6 +----- .../rnscreens/ScreenStackFragment.kt | 21 +++++++++++++++++++ .../rnscreens/ScreenStackViewManager.kt | 1 - 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 7dea8cb020..6b4d1d733c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -50,16 +50,12 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { override fun startViewTransition(view: View) { super.startViewTransition(view) removalTransitionStarted = true - previousTopScreenWrapper?.onViewAnimationStart() - topScreenWrapper?.onViewAnimationStart() } override fun endViewTransition(view: View) { super.endViewTransition(view) if (removalTransitionStarted) { removalTransitionStarted = false - previousTopScreenWrapper?.onViewAnimationEnd() - topScreenWrapper?.onViewAnimationEnd() dispatchOnFinishTransitioning() } } @@ -344,7 +340,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { // For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet. // Hence, we're comparing numerical version here. // Build.VERSION.SDK_INT >= 33 || - fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || + fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.IOS } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index f57336403f..88fce67a87 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -1,6 +1,8 @@ package com.swmansion.rnscreens import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet import android.annotation.SuppressLint import android.content.Context import android.graphics.Color @@ -89,6 +91,25 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { notifyViewAppearTransitionEnd() } + override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator { + val listener = object: AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + onViewAnimationStart() + super.onAnimationStart(animation) + } + + override fun onAnimationEnd(animation: Animator) { + onViewAnimationEnd() + super.onAnimationEnd(animation) + } + } + + val animator = AnimatorSet().apply { + addListener(listener) + } + return animator + } + override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { // this means that the fragment will appear with a custom transition, in the case // of animation: 'none', onViewAnimationStart and onViewAnimationEnd diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt index d3b605bfc1..cbbf0d5209 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt @@ -26,7 +26,6 @@ class ScreenStackViewManager : ViewGroupManager(), RNSScreenStackMa override fun addView(parent: ScreenStack, child: View, index: Int) { require(child is Screen) { "Attempt attach child that is not of type RNScreen" } - prepareOutTransition(child) parent.addScreen(child, index) } From 4a1d0d6b3f6d3a9dadb1acb6de38ee8b3e5c78d4 Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 24 Apr 2024 18:07:33 +0200 Subject: [PATCH 4/8] Fix animator in onCreateAnimator --- .../rnscreens/ScreenStackFragment.kt | 34 ++++++++++++++++--- src/native-stack/views/NativeStackView.tsx | 4 --- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 88fce67a87..7b1ed69d6b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -1,13 +1,13 @@ package com.swmansion.rnscreens import android.animation.Animator +import android.animation.AnimatorInflater import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -20,6 +20,8 @@ import android.view.animation.Transformation import android.widget.LinearLayout import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.fragment.R +import androidx.fragment.app.FragmentTransaction import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.PixelUtil import com.google.android.material.appbar.AppBarLayout @@ -91,8 +93,11 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { notifyViewAppearTransitionEnd() } - override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator { - val listener = object: AnimatorListenerAdapter() { + // Similarly to ScreensCoordinatorLayout, this method listens only for the phases of default Android + // transitions (default/none/fade), since `ScreensCoordinatorLayout#startAnimation` is being + // called only for custom animations. + override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? { + val listener = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { onViewAnimationStart() super.onAnimationStart(animation) @@ -104,9 +109,28 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { } } - val animator = AnimatorSet().apply { + // When fragment is being removed or there's no transition selected, we simply + // return AnimatorSet without any animation. + if (isRemoving || transit == FragmentTransaction.TRANSIT_NONE) { + return AnimatorSet().apply { + addListener(listener) + } + } + + var selectedNextAnim = nextAnim + if (nextAnim == 0) { + selectedNextAnim = when (transit) { + FragmentTransaction.TRANSIT_FRAGMENT_OPEN -> if (enter) R.animator.fragment_open_enter else R.animator.fragment_open_exit + FragmentTransaction.TRANSIT_FRAGMENT_CLOSE -> if (enter) R.animator.fragment_close_enter else R.animator.fragment_close_exit + FragmentTransaction.TRANSIT_FRAGMENT_FADE -> if (enter) R.animator.fragment_fade_enter else R.animator.fragment_fade_exit + else -> 0 + } + } + + val animator = AnimatorInflater.loadAnimator(context, selectedNextAnim).apply { addListener(listener) } + return animator } @@ -116,7 +140,7 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { // won't be called and we need to notify stack directly from here. // When using the Toolbar back button this is called an extra time with transit = 0 but in // this case we don't want to notify. The way I found to detect is case is check isHidden. - if (transit == 0 && !isHidden + if (transit == 0 && !isHidden // && screen.stackAnimation === Screen.StackAnimation.NONE ) { if (enter) { diff --git a/src/native-stack/views/NativeStackView.tsx b/src/native-stack/views/NativeStackView.tsx index d60896eeb3..6d3e82f818 100644 --- a/src/native-stack/views/NativeStackView.tsx +++ b/src/native-stack/views/NativeStackView.tsx @@ -324,7 +324,6 @@ const RouteView = ({ data: { closing: false }, target: route.key, }); - console.error(`SCREEN ${index}:Will appear`); }} onWillDisappear={() => { navigation.emit({ @@ -332,14 +331,12 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); - console.error(`SCREEN ${index}:Will disappear`); }} onAppear={() => { navigation.emit({ type: 'appear', target: route.key, }); - console.error(`SCREEN ${index}:Appear`); navigation.emit({ type: 'transitionEnd', data: { closing: false }, @@ -352,7 +349,6 @@ const RouteView = ({ data: { closing: true }, target: route.key, }); - console.error(`SCREEN ${index}:Disappear`); }} onHeaderHeightChange={e => { const headerHeight = e.nativeEvent.headerHeight; From f49ae2b597f8e0afa9753822f37a014086eb4450 Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 24 Apr 2024 18:22:05 +0200 Subject: [PATCH 5/8] Remove unused code --- TestsExample/App.js | 2 +- TestsExample/src/Test1072.tsx | 11 +--- .../java/com/swmansion/rnscreens/Screen.kt | 9 --- .../com/swmansion/rnscreens/ScreenFragment.kt | 10 ++-- .../com/swmansion/rnscreens/ScreenStack.kt | 2 - .../rnscreens/ScreenStackFragment.kt | 58 +++++++++---------- 6 files changed, 38 insertions(+), 54 deletions(-) diff --git a/TestsExample/App.js b/TestsExample/App.js index e5c85f0b60..b34e9d99f1 100644 --- a/TestsExample/App.js +++ b/TestsExample/App.js @@ -100,5 +100,5 @@ import Test2069 from './src/Test2069'; enableFreeze(true); export default function App() { - return ; + return ; } diff --git a/TestsExample/src/Test1072.tsx b/TestsExample/src/Test1072.tsx index cc4b969279..7535c82dd8 100644 --- a/TestsExample/src/Test1072.tsx +++ b/TestsExample/src/Test1072.tsx @@ -28,17 +28,13 @@ function First({ ); } -function Second({ - navigation, -}: { - navigation: NativeStackNavigationProp; -}) { +function Second() { return ( Use swipe back gesture to go back (iOS only) - navigation.navigate('Third')} /> + ); } @@ -50,12 +46,11 @@ export default function App() { - diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 244fe640b4..b31e3788e1 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -6,7 +6,6 @@ import android.graphics.Paint import android.os.Parcelable import android.util.SparseArray import android.util.TypedValue -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.webkit.WebView @@ -51,14 +50,6 @@ class Screen(context: ReactContext?) : FabricEnabledViewGroup(context) { layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION) } - override fun startViewTransition(view: View?) { - super.startViewTransition(view) - } - - override fun endViewTransition(view: View?) { - super.endViewTransition(view) - } - override fun dispatchSaveInstanceState(container: SparseArray) { // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize // view's states. The side effect of restoring is that TextInput components would trigger diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt index a9312f10ac..e4a81ff5d8 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -174,22 +174,22 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { } } - fun dispatchOnWillAppear() { + private fun dispatchOnWillAppear() { dispatchLifecycleEvent(ScreenLifecycleEvent.WILL_APPEAR, this) dispatchTransitionProgressEvent(0.0f, false) } - fun dispatchOnAppear() { + private fun dispatchOnAppear() { dispatchLifecycleEvent(ScreenLifecycleEvent.DID_APPEAR, this) dispatchTransitionProgressEvent(1.0f, false) } - fun dispatchOnWillDisappear() { + private fun dispatchOnWillDisappear() { dispatchLifecycleEvent(ScreenLifecycleEvent.WILL_DISAPPEAR, this) dispatchTransitionProgressEvent(0.0f, true) } - fun dispatchOnDisappear() { + private fun dispatchOnDisappear() { dispatchLifecycleEvent(ScreenLifecycleEvent.DID_DISAPPEAR, this) dispatchTransitionProgressEvent(1.0f, true) } @@ -276,7 +276,7 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root // view. We override an appropriate method of the StackFragment's // root view in order to achieve this. - if (isResumed || screen.container?.topScreen === screen) { + if (isResumed) { // Android dispatches the animation start event for the fragment that is being added first // however we want the one being dismissed first to match iOS. It also makes more sense from // a navigation point of view to have the disappear event first. diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 6b4d1d733c..95740dd975 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -16,7 +16,6 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { private val drawingOpPool: MutableList = ArrayList() private var drawingOps: MutableList = ArrayList() private var topScreenWrapper: ScreenStackFragmentWrapper? = null - private var previousTopScreenWrapper: ScreenStackFragmentWrapper? = null private var removalTransitionStarted = false private var isDetachingCurrentScreen = false private var reverseLastTwoChildren = false @@ -217,7 +216,6 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { } else if (newTop != null && !newTop.fragment.isAdded) { it.add(id, newTop.fragment) } - previousTopScreenWrapper = topScreenWrapper topScreenWrapper = newTop as? ScreenStackFragmentWrapper stack.clear() stack.addAll(screenWrappers.map { it as ScreenStackFragmentWrapper }) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 7b1ed69d6b..19f1962ac7 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -134,34 +134,34 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { return animator } - override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { - // this means that the fragment will appear with a custom transition, in the case - // of animation: 'none', onViewAnimationStart and onViewAnimationEnd - // won't be called and we need to notify stack directly from here. - // When using the Toolbar back button this is called an extra time with transit = 0 but in - // this case we don't want to notify. The way I found to detect is case is check isHidden. - if (transit == 0 && !isHidden -// && screen.stackAnimation === Screen.StackAnimation.NONE - ) { - if (enter) { - // Android dispatches the animation start event for the fragment that is being added first - // however we want the one being dismissed first to match iOS. It also makes more sense - // from a navigation point of view to have the disappear event first. - // Since there are no explicit relationships between the fragment being added / removed - // the practical way to fix this is delaying dispatching the appear events at the end of - // the frame. - UiThreadUtil.runOnUiThread { - dispatchOnWillAppear() - dispatchOnAppear() - } - } else { - dispatchOnWillDisappear() - dispatchOnDisappear() - notifyViewAppearTransitionEnd() - } - } - return null - } +// override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { +// // this means that the fragment will appear with a custom transition, in the case +// // of animation: 'none', onViewAnimationStart and onViewAnimationEnd +// // won't be called and we need to notify stack directly from here. +// // When using the Toolbar back button this is called an extra time with transit = 0 but in +// // this case we don't want to notify. The way I found to detect is case is check isHidden. +// if (transit == 0 && !isHidden +//// && screen.stackAnimation === Screen.StackAnimation.NONE +// ) { +// if (enter) { +// // Android dispatches the animation start event for the fragment that is being added first +// // however we want the one being dismissed first to match iOS. It also makes more sense +// // from a navigation point of view to have the disappear event first. +// // Since there are no explicit relationships between the fragment being added / removed +// // the practical way to fix this is delaying dispatching the appear events at the end of +// // the frame. +// UiThreadUtil.runOnUiThread { +// dispatchOnWillAppear() +// dispatchOnAppear() +// } +// } else { +// dispatchOnWillDisappear() +// dispatchOnDisappear() +// notifyViewAppearTransitionEnd() +// } +// } +// return null +// } private fun notifyViewAppearTransitionEnd() { val screenStack = view?.parent @@ -289,7 +289,7 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { container.dismiss(this) } - class ScreensCoordinatorLayout( + private class ScreensCoordinatorLayout( context: Context, private val mFragment: ScreenFragment ) : CoordinatorLayout(context) { From 4f5119729453f96201a9380a7eaa5d40dabfaf6d Mon Sep 17 00:00:00 2001 From: tboba Date: Wed, 24 Apr 2024 18:28:40 +0200 Subject: [PATCH 6/8] Add another needed code --- .../com/swmansion/rnscreens/ScreenFragment.kt | 6 +--- .../rnscreens/ScreenStackFragment.kt | 29 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt index e4a81ff5d8..2fc1ac40a5 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -8,12 +8,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewParent -import android.view.animation.Animation import android.widget.FrameLayout import androidx.fragment.app.Fragment -import androidx.transition.Transition -import androidx.transition.Transition.TransitionListener -import androidx.transition.TransitionSet import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.UIManagerHelper @@ -276,7 +272,7 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper { // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root // view. We override an appropriate method of the StackFragment's // root view in order to achieve this. - if (isResumed) { + if (isResumed || screen.container?.topScreen === screen) { // Android dispatches the animation start event for the fragment that is being added first // however we want the one being dismissed first to match iOS. It also makes more sense from // a navigation point of view to have the disappear event first. diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 19f1962ac7..fb3773d5be 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -134,35 +134,6 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { return animator } -// override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { -// // this means that the fragment will appear with a custom transition, in the case -// // of animation: 'none', onViewAnimationStart and onViewAnimationEnd -// // won't be called and we need to notify stack directly from here. -// // When using the Toolbar back button this is called an extra time with transit = 0 but in -// // this case we don't want to notify. The way I found to detect is case is check isHidden. -// if (transit == 0 && !isHidden -//// && screen.stackAnimation === Screen.StackAnimation.NONE -// ) { -// if (enter) { -// // Android dispatches the animation start event for the fragment that is being added first -// // however we want the one being dismissed first to match iOS. It also makes more sense -// // from a navigation point of view to have the disappear event first. -// // Since there are no explicit relationships between the fragment being added / removed -// // the practical way to fix this is delaying dispatching the appear events at the end of -// // the frame. -// UiThreadUtil.runOnUiThread { -// dispatchOnWillAppear() -// dispatchOnAppear() -// } -// } else { -// dispatchOnWillDisappear() -// dispatchOnDisappear() -// notifyViewAppearTransitionEnd() -// } -// } -// return null -// } - private fun notifyViewAppearTransitionEnd() { val screenStack = view?.parent if (screenStack is ScreenStack) { From 8cebd590e1fc8338c094b5b57933b5a73f7aebff Mon Sep 17 00:00:00 2001 From: tboba Date: Thu, 25 Apr 2024 10:08:02 +0200 Subject: [PATCH 7/8] Fix custom animations --- android/src/main/java/com/swmansion/rnscreens/Screen.kt | 6 ++++++ .../java/com/swmansion/rnscreens/ScreenStackFragment.kt | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index b31e3788e1..8fc1714b55 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -278,4 +278,10 @@ class Screen(context: ReactContext?) : FabricEnabledViewGroup(context) { enum class WindowTraits { ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN } + + companion object { + fun isSystemAnimation(stackAnimation: StackAnimation): Boolean { + return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE + } + } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index fb3773d5be..cdf2896a3c 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -109,6 +109,12 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { } } + // If there's custom animation set, use default onCreateAnimator implementation, as event + // handling will be handled by ScreensCoordinatorLayout. + if (!Screen.isSystemAnimation(screen.stackAnimation)) { + return super.onCreateAnimator(transit, enter, nextAnim) + } + // When fragment is being removed or there's no transition selected, we simply // return AnimatorSet without any animation. if (isRemoving || transit == FragmentTransaction.TRANSIT_NONE) { From 5476bf9948e60184d20b34fe1e35e47d46bf65c3 Mon Sep 17 00:00:00 2001 From: tboba Date: Thu, 25 Apr 2024 10:20:09 +0200 Subject: [PATCH 8/8] Uncomment checking build version --- android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 95740dd975..9a6c4b9e9b 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -2,6 +2,7 @@ package com.swmansion.rnscreens import android.content.Context import android.graphics.Canvas +import android.os.Build import android.view.View import androidx.fragment.app.FragmentTransaction import com.facebook.react.bridge.ReactContext @@ -337,8 +338,8 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { // On Android sdk 33 and above the animation is different and requires draw reordering. // For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet. // Hence, we're comparing numerical version here. -// Build.VERSION.SDK_INT >= 33 || - fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || + Build.VERSION.SDK_INT >= 33 || + fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM || fragmentWrapper.screen.stackAnimation === StackAnimation.IOS }