diff --git a/native/chat/reaction-selection-popover.react.js b/native/chat/reaction-selection-popover.react.js --- a/native/chat/reaction-selection-popover.react.js +++ b/native/chat/reaction-selection-popover.react.js @@ -52,6 +52,7 @@ 'ReactionSelectionPopover should have OverlayContext', ); const { position } = overlayContext; + invariant(position, 'position should be defined in tooltip'); const dimensions = useSelector(state => state.dimensions); diff --git a/native/components/full-screen-view-modal.react.js b/native/components/full-screen-view-modal.react.js --- a/native/components/full-screen-view-modal.react.js +++ b/native/components/full-screen-view-modal.react.js @@ -251,6 +251,7 @@ const { overlayContext } = props; invariant(overlayContext, 'FullScreenViewModal should have OverlayContext'); const navigationProgress = overlayContext.position; + invariant(navigationProgress, 'position should be defined in tooltip'); // The inputs we receive from PanGestureHandler const panState = new Value(-1); diff --git a/native/media/video-playback-modal.react.js b/native/media/video-playback-modal.react.js --- a/native/media/video-playback-modal.react.js +++ b/native/media/video-playback-modal.react.js @@ -481,6 +481,7 @@ const overlayContext = React.useContext(OverlayContext); invariant(overlayContext, 'VideoPlaybackModal should have OverlayContext'); const navigationProgress = overlayContext.position; + invariant(navigationProgress, 'position should be defined in tooltip'); const reverseNavigationProgress = React.useMemo( () => sub(1, navigationProgress), diff --git a/native/navigation/overlay-context.js b/native/navigation/overlay-context.js --- a/native/navigation/overlay-context.js +++ b/native/navigation/overlay-context.js @@ -6,7 +6,9 @@ export type VisibleOverlay = { +routeKey: string, +routeName: string, - +position: Animated.Value, + +position: ?Animated.Value, + +renderChild: boolean, + +animationCallback?: () => void, +presentedFrom: ?string, }; @@ -14,7 +16,9 @@ export type OverlayContextType = { // position and isDismissing are local to the current route - +position: Animated.Node, + +position: ?Animated.Node, + +renderChild: boolean, + +animationCallback?: () => void, +isDismissing: boolean, // The rest are global to the entire OverlayNavigator +visibleOverlays: $ReadOnlyArray, 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 @@ -36,9 +36,15 @@ OverlayRouterExtraNavigationHelpers, OverlayRouterNavigationAction, } from './overlay-router.js'; -import { scrollBlockingModals, TabNavigatorRouteName } from './route-names.js'; +import { + scrollBlockingModals, + TabNavigatorRouteName, + NUXTipsOverlayRouteName, +} from './route-names.js'; import { isMessageTooltipKey } from '../chat/utils.js'; +const newReanimatedRoutes = new Set([NUXTipsOverlayRouteName]); + export type OverlayNavigationHelpers = { ...$Exact>, @@ -60,7 +66,9 @@ +route: Route<>, +descriptor: Descriptor, {}>, +context: { - +position: Value, + +position: ?Value, + +renderChild: boolean, + +animationCallback?: () => void, +isDismissing: boolean, }, +ordering: { @@ -127,7 +135,9 @@ descriptor, `OverlayNavigator could not find descriptor for ${route.key}`, ); - if (!positions[route.key]) { + const shouldUseLegacyAnimation = !newReanimatedRoutes.has(route.name); + + if (!positions[route.key] && shouldUseLegacyAnimation) { positions[route.key] = new Value(firstRender ? 1 : 0); } return { @@ -136,6 +146,7 @@ context: { position: positions[route.key], isDismissing: curIndex < routeIndex, + renderChild: true, }, ordering: { routeIndex, @@ -166,6 +177,7 @@ routeKey: route.key, routeName: route.name, position: positions[route.key], + renderChild: true, presentedFrom, }; }; @@ -341,50 +353,47 @@ // A route just got dismissed // We'll watch the animation to determine when to clear the screen const { position } = data.context; - invariant(position, `should have position for dismissed key ${key}`); + 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, + renderChild: false, + animationCallback: removeScreen, }, - listeners: [ - cond( - lessOrEq(position, 0), - call([], () => { - // 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; - }); - }), - ), - ], + listeners, }; sceneDataChanged = true; queueAnimation(key, 0); @@ -413,8 +422,12 @@ return; } for (const key in pendingAnimations) { - const toValue = pendingAnimations[key]; const position = positions[key]; + if (!position) { + continue; + } + const toValue = pendingAnimations[key]; + let duration = 150; if (isMessageTooltipKey(key)) { const navigationTransitionSpec = @@ -426,7 +439,6 @@ navigationTransitionSpec.config.duration) || 400; } - invariant(position, `should have position for animating key ${key}`); timing(position, { duration, easing: EasingNode.inOut(EasingNode.ease), diff --git a/native/tooltip/nux-tips-overlay.react.js b/native/tooltip/nux-tips-overlay.react.js --- a/native/tooltip/nux-tips-overlay.react.js +++ b/native/tooltip/nux-tips-overlay.react.js @@ -90,7 +90,8 @@ const dimensions = useSelector(state => state.dimensions); const overlayContext = React.useContext(OverlayContext); invariant(overlayContext, 'NUXTipsOverlay should have OverlayContext'); - const { position } = overlayContext; + + const position = React.useMemo(() => new Animated.Value(1), []); const { navigation, route } = props; diff --git a/native/tooltip/tooltip.react.js b/native/tooltip/tooltip.react.js --- a/native/tooltip/tooltip.react.js +++ b/native/tooltip/tooltip.react.js @@ -181,6 +181,7 @@ const { overlayContext } = props; invariant(overlayContext, 'Tooltip should have OverlayContext'); const { position } = overlayContext; + invariant(position, 'position should be defined in tooltip'); this.backdropOpacity = interpolateNode(position, { inputRange: [0, 1], @@ -432,6 +433,8 @@ invariant(overlayContext, 'Tooltip should have OverlayContext'); const { position } = overlayContext; + invariant(position, 'position should be defined in tooltip'); + const isOpeningSidebar = !!chatContext?.currentTransitionSidebarSourceID; const buttonProps: ButtonProps = {