-
-
Notifications
You must be signed in to change notification settings - Fork 544
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(Android): missing transition events with formSheet presentation (#…
…2682) ## Description This PR aligns* events received in element tree between Android and iOS for screens with `presentation: 'formSheet'`. \* **almost aligns** - on iOS when natively dismissed `onDismissed` and `onDisappear/transitionEnd` will be received, whereas on Android it will be only `onDismissed` for now. Adding support for `onDisappear/transitionEnd` requires wider changes in logic which I don't want in incoming 4.7.0 release. ## Changes `formSheet` animations based on custom animators now have listener set, which emmits appropriate events **bypassing** "regular" view emitting infrastructure. I've found that `ScreenFragment.onViewAnimation{Start,End}` methods do not work for some reason (`isResumed` is false for form sheet) with the sheets, therefore I bypassed it for now. ## Test code and steps to reproduce `TestAndroidTransition` + add logs in `NativeStackView` in `react-navigation` to see when events are received (or attach appropriate listeners). ## Checklist - [ ] Ensured that CI passes
- Loading branch information
Showing
4 changed files
with
134 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
android/src/main/java/com/swmansion/rnscreens/events/ScreenAnimationDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
48 changes: 0 additions & 48 deletions
48
android/src/main/java/com/swmansion/rnscreens/events/ScreenEventDelegate.kt
This file was deleted.
Oops, something went wrong.
36 changes: 36 additions & 0 deletions
36
android/src/main/java/com/swmansion/rnscreens/events/ScreenEventEmitter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
} | ||
} |