diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index bb3754748..f3d0009cb 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -45,8 +45,9 @@ import com.swmansion.rnscreens.bottomsheet.useSingleDetent import com.swmansion.rnscreens.bottomsheet.useThreeDetents import com.swmansion.rnscreens.bottomsheet.useTwoDetents import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation +import com.swmansion.rnscreens.events.ScreenAnimationDelegate import com.swmansion.rnscreens.events.ScreenDismissedEvent -import com.swmansion.rnscreens.events.ScreenEventDelegate +import com.swmansion.rnscreens.events.ScreenEventEmitter import com.swmansion.rnscreens.ext.recycle import com.swmansion.rnscreens.transition.ExternalBoundaryValuesEvaluator import com.swmansion.rnscreens.utils.DeviceUtils @@ -366,7 +367,14 @@ class ScreenStackFragment : } animatorSet.play(alphaAnimator).with(slideAnimator) } - animatorSet.addListener(ScreenEventDelegate(this)) + animatorSet.addListener( + ScreenAnimationDelegate( + this, + ScreenEventEmitter(this.screen), + if (enter) ScreenAnimationDelegate.AnimationType.ENTER + else ScreenAnimationDelegate.AnimationType.EXIT + ) + ) return animatorSet } diff --git a/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt new file mode 100644 index 000000000..34fedac16 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt @@ -0,0 +1,88 @@ +package com.swmansion.rnscreens.events + +import android.animation.Animator +import android.util.Log +import com.swmansion.rnscreens.ScreenStackFragmentWrapper + +// The goal is to make this universal delegate for handling animation progress related logic. +// At this moment this class works only with form sheet presentation. +class ScreenAnimationDelegate( + private val wrapper: ScreenStackFragmentWrapper, + private val eventEmitter: ScreenEventEmitter?, + private val animationType: AnimationType, +) : Animator.AnimatorListener { + enum class AnimationType { + ENTER, + EXIT + } + + private var currentState: LifecycleState = LifecycleState.INITIALIZED + + private fun progressState() { + currentState = + when (currentState) { + LifecycleState.INITIALIZED -> LifecycleState.START_DISPATCHED + LifecycleState.START_DISPATCHED -> LifecycleState.END_DISPATCHED + LifecycleState.END_DISPATCHED -> LifecycleState.END_DISPATCHED + } + } + + override fun onAnimationStart(animation: Animator) { + if (currentState === LifecycleState.INITIALIZED) { + progressState() + + // These callbacks do not work as expected from this call site, TODO: investigate it. + // To fix it quickly we emit required events manually +// wrapper.onViewAnimationStart() + + when (animationType) { + AnimationType.ENTER -> eventEmitter?.dispatchOnWillAppear() + AnimationType.EXIT -> eventEmitter?.dispatchOnWillDisappear() + } + + val isExitAnimation = animationType === AnimationType.EXIT + eventEmitter?.dispatchTransitionProgress( + 0.0f, + isExitAnimation, + isExitAnimation, + ) + } + } + + override fun onAnimationEnd(animation: Animator) { + if (currentState === LifecycleState.START_DISPATCHED) { + progressState() + animation.removeListener(this) + +// wrapper.onViewAnimationEnd() + + when (animationType) { + AnimationType.ENTER -> eventEmitter?.dispatchOnAppear() + AnimationType.EXIT -> eventEmitter?.dispatchOnDisappear() + } + + val isExitAnimation = animationType === AnimationType.EXIT + eventEmitter?.dispatchTransitionProgress( + 1.0f, + isExitAnimation, + isExitAnimation, + ) + + wrapper.screen.endRemovalTransition() + } + } + + override fun onAnimationCancel(animation: Animator) = Unit + + override fun onAnimationRepeat(animation: Animator) = Unit + + private enum class LifecycleState { + INITIALIZED, + START_DISPATCHED, + END_DISPATCHED, + } + + companion object { + const val TAG = "ScreenEventDelegate" + } +} diff --git a/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventDelegate.kt b/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventDelegate.kt deleted file mode 100644 index af79b6483..000000000 --- a/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventDelegate.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.swmansion.rnscreens.events - -import android.animation.Animator -import com.swmansion.rnscreens.ScreenFragmentWrapper - -class ScreenEventDelegate( - private val wrapper: ScreenFragmentWrapper, -) : Animator.AnimatorListener { - private var currentState: LifecycleState = LifecycleState.INITIALIZED - - private fun progressState() { - currentState = - when (currentState) { - LifecycleState.INITIALIZED -> LifecycleState.START_DISPATCHED - LifecycleState.START_DISPATCHED -> LifecycleState.END_DISPATCHED - LifecycleState.END_DISPATCHED -> LifecycleState.END_DISPATCHED - } - } - - override fun onAnimationStart(animation: Animator) { - if (currentState === LifecycleState.INITIALIZED) { - progressState() - wrapper.onViewAnimationStart() - } - } - - override fun onAnimationEnd(animation: Animator) { - if (currentState === LifecycleState.START_DISPATCHED) { - progressState() - animation.removeListener(this) - wrapper.onViewAnimationEnd() - } - } - - override fun onAnimationCancel(animation: Animator) = Unit - - override fun onAnimationRepeat(animation: Animator) = Unit - - private enum class LifecycleState { - INITIALIZED, - START_DISPATCHED, - END_DISPATCHED, - } - - companion object { - const val TAG = "ScreenEventDelegate" - } -} diff --git a/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventEmitter.kt b/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventEmitter.kt new file mode 100644 index 000000000..ae32b2139 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventEmitter.kt @@ -0,0 +1,36 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.uimanager.UIManagerHelper +import com.swmansion.rnscreens.Screen +import com.swmansion.rnscreens.ScreenFragment + +// TODO: Consider taking weak ref here or accepting screen as argument in every method +// to avoid reference cycle. +class ScreenEventEmitter(val screen: Screen) { + val reactEventDispatcher + get() = screen.reactEventDispatcher + + val reactSurfaceId + get() = UIManagerHelper.getSurfaceId(screen) + + fun dispatchOnWillAppear() = + reactEventDispatcher?.dispatchEvent(ScreenWillAppearEvent(reactSurfaceId, screen.id)) + + fun dispatchOnAppear() = + reactEventDispatcher?.dispatchEvent(ScreenAppearEvent(reactSurfaceId, screen.id)) + + fun dispatchOnWillDisappear() = + reactEventDispatcher?.dispatchEvent(ScreenWillDisappearEvent(reactSurfaceId, screen.id)) + + fun dispatchOnDisappear() = + reactEventDispatcher?.dispatchEvent(ScreenDisappearEvent(reactSurfaceId, screen.id)) + + fun dispatchOnDismissed() = + reactEventDispatcher?.dispatchEvent(ScreenDismissedEvent(reactSurfaceId, screen.id)) + + fun dispatchTransitionProgress(progress: Float, isExitAnimation: Boolean, isGoingForward: Boolean) { + val sanitizedProgress = progress.coerceIn(0.0f, 1.0f) + val coalescingKey = ScreenFragment.getCoalescingKey(sanitizedProgress) + reactEventDispatcher?.dispatchEvent(ScreenTransitionProgressEvent(reactSurfaceId, screen.id, sanitizedProgress, isExitAnimation, isGoingForward, coalescingKey)) + } +}