diff --git a/native/navigation/overlay-navigator.react.js b/native/navigation/overlay-navigator.react.js --- a/native/navigation/overlay-navigator.react.js +++ b/native/navigation/overlay-navigator.react.js @@ -27,6 +27,7 @@ EasingNode, cancelAnimation, makeMutable, + runOnJS, withTiming, } from 'react-native-reanimated'; import type { SharedValue } from 'react-native-reanimated'; @@ -77,7 +78,7 @@ ...OverlayRouterExtraNavigationHelpers, }; -const { Value, timing, cond, call, lessOrEq, block } = Animated; +const { Value, timing } = Animated; type Scene = { +route: Route<>, @@ -107,7 +108,6 @@ ...$PropertyType, +creationTime: number, }>, - +listeners: $ReadOnlyArray, }>; type Props = $Exact< @@ -272,7 +272,6 @@ ...scene.ordering, creationTime: Date.now(), }, - listeners: [], }); // We track two previous states of scrollBlockingModalStatus via refs. We @@ -315,6 +314,36 @@ }; }; + const removeScreen = (key: string) => { + // This gets called when the scene is no longer visible and + // handles cleaning up our data structures to remove it + const curVisibleOverlays = visibleOverlaysRef.current; + invariant(curVisibleOverlays, 'visibleOverlaysRef should be set'); + const newVisibleOverlays = curVisibleOverlays.filter( + (overlay: VisibleOverlay) => overlay.routeKey !== key, + ); + if (newVisibleOverlays.length === curVisibleOverlays.length) { + return; + } + visibleOverlaysRef.current = newVisibleOverlays; + setSceneData(curSceneData => { + const newSceneData: { [string]: SceneData } = {}; + for (const sceneKey in curSceneData) { + if (sceneKey === key) { + continue; + } + newSceneData[sceneKey] = { + ...curSceneData[sceneKey], + context: { + ...curSceneData[sceneKey].context, + visibleOverlays: newVisibleOverlays, + }, + }; + } + return newSceneData; + }); + }; + // This block keeps sceneData updated when our props change. It's the // hook equivalent of getDerivedStateFromProps // https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops @@ -389,50 +418,14 @@ continue; } - // A route just got dismissed - // We'll watch the animation to determine when to clear the screen - const { position } = data.context; - const removeScreen = () => { - // This gets called when the scene is no longer visible and - // handles cleaning up our data structures to remove it - const curVisibleOverlays = visibleOverlaysRef.current; - invariant(curVisibleOverlays, 'visibleOverlaysRef should be set'); - const newVisibleOverlays = curVisibleOverlays.filter( - (overlay: VisibleOverlay) => overlay.routeKey !== key, - ); - if (newVisibleOverlays.length === curVisibleOverlays.length) { - return; - } - visibleOverlaysRef.current = newVisibleOverlays; - setSceneData(curSceneData => { - const newSceneData: { [string]: SceneData } = {}; - for (const sceneKey in curSceneData) { - if (sceneKey === key) { - continue; - } - newSceneData[sceneKey] = { - ...curSceneData[sceneKey], - context: { - ...curSceneData[sceneKey].context, - visibleOverlays: newVisibleOverlays, - }, - }; - } - return newSceneData; - }); - }; - const listeners = position - ? [cond(lessOrEq(position, 0), call([], removeScreen))] - : []; updatedSceneData[key] = { ...data, context: { ...data.context, isDismissing: true, shouldRenderScreenContent: false, - onExitFinish: removeScreen, + onExitFinish: () => removeScreen(key), }, - listeners, }; sceneDataChanged = true; queueAnimation(key, 0); @@ -487,10 +480,18 @@ easing: EasingNode.inOut(EasingNode.ease), toValue, }).start(); - positionV2.value = withTiming(toValue, { - duration, - easing: Easing.inOut(Easing.ease), - }); + positionV2.value = withTiming( + toValue, + { + duration, + easing: Easing.inOut(Easing.ease), + }, + () => { + if (positionV2.value <= 0) { + runOnJS(removeScreen)(key); + } + }, + ); } pendingAnimationsRef.current = {}; }, [positions, positionsV2, pendingAnimations]); @@ -562,21 +563,17 @@ }); const screens = sceneList.map(scene => { - const { route, descriptor, context, listeners } = scene; + const { route, descriptor, context } = scene; const { render } = descriptor; const pressable = !context.isDismissing && !route.params?.preventPresses; const pointerEvents = pressable ? 'auto' : 'none'; - // These listeners are used to clear routes after they finish dismissing - const listenerCode = - listeners.length > 0 ? : null; return ( {render()} - {listenerCode} ); });