react-native#InteractionManager TypeScript Examples

The following examples show how to use react-native#InteractionManager. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: CachedImage.tsx    From lexicon with MIT License 6 votes vote down vote up
async componentDidMount() {
    this._interaction = InteractionManager.runAfterInteractions(async () => {
      if (this.props.source.uri) {
        const filesystemURI = await this.getImageFilesystemKey(
          this.props.source.uri,
        );
        await this.loadImage(filesystemURI, this.props.source.uri);
      }
    });
  }
Example #2
Source File: use-after-interactions.ts    From react-navigation-heavy-screen with MIT License 6 votes vote down vote up
useAfterInteractions = () => {
	const [areInteractionsComplete, setInteractionsComplete] = useState(false)

	const subscriptionRef = useRef<ReturnType<
		typeof InteractionManager.runAfterInteractions
	> | null>(null)

	const transitionRef = useRef<TransitioningView>(null)

	useEffect(() => {
		subscriptionRef.current = InteractionManager.runAfterInteractions(
			() => {
				transitionRef.current?.animateNextTransition()
				setInteractionsComplete(true)
				subscriptionRef.current = null
			}
		)
		return () => {
			subscriptionRef.current?.cancel()
		}
	}, [])

	return {
		areInteractionsComplete,
		transitionRef,
	}
}
Example #3
Source File: DevPersistedNavigationContainer.tsx    From mobile with Apache License 2.0 5 votes vote down vote up
function DevPersistedNavigationContainerImpl(
  {persistKey, onStateChange, ...others}: DevPersistedNavigationContainerProps,
  forwardedRef: React.Ref<NavigationContainerRef>,
) {
  const [isReady, setIsReady] = React.useState(false);
  const [initialState, setInitialState] = React.useState<InitialState | undefined>();
  const persistInteractionRef = React.useRef<{cancel: () => void} | null>(null);
  const onStateChangeInternal = React.useCallback(
    state => {
      const persistState = async () => {
        persistInteractionRef.current = null;
        try {
          await AsyncStorage.setItem(persistKey, JSON.stringify(state));
        } catch (ex) {
          console.warn(`Failed to persist state. ${ex.message}`);
        }
      };

      if (persistInteractionRef.current !== null) {
        persistInteractionRef.current.cancel();
      }

      if (state != null) {
        persistInteractionRef.current = InteractionManager.runAfterInteractions(persistState);
      }

      if (onStateChange != null) {
        onStateChange(state);
      }
    },
    [onStateChange, persistKey],
  );

  React.useEffect(() => {
    const loadPersistedState = async () => {
      try {
        const jsonString = await AsyncStorage.getItem(persistKey);
        if (jsonString != null) {
          setInitialState(JSON.parse(jsonString));
        }
        setIsReady(true);
      } catch (ex) {
        console.warn(`Failed to load state. ${ex.message}`);
        setIsReady(true);
      }
    };
    loadPersistedState();
  }, [persistKey]);

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      {...others}
      key={persistKey}
      ref={forwardedRef}
      initialState={initialState}
      onStateChange={onStateChangeInternal}
    />
  );
}
Example #4
Source File: DevPersistedNavigationContainer.tsx    From mobile with Apache License 2.0 5 votes vote down vote up
function DevPersistedNavigationContainerImpl(
  {persistKey, onStateChange, ...others}: DevPersistedNavigationContainerProps,
  forwardedRef: React.Ref<NavigationContainerRef>,
) {
  const [isReady, setIsReady] = React.useState(false);
  const [initialState, setInitialState] = React.useState<InitialState | undefined>();
  const persistInteractionRef = React.useRef<{cancel: () => void} | null>(null);
  const onStateChangeInternal = React.useCallback(
    state => {
      const persistState = async () => {
        persistInteractionRef.current = null;
        try {
          await AsyncStorage.setItem(persistKey, JSON.stringify(state));
        } catch (error) {
          captureException(`Failed to persist state.`, error);
        }
      };

      if (persistInteractionRef.current !== null) {
        persistInteractionRef.current.cancel();
      }

      if (state != null) {
        persistInteractionRef.current = InteractionManager.runAfterInteractions(persistState);
      }

      if (onStateChange != null) {
        onStateChange(state);
      }
    },
    [onStateChange, persistKey],
  );

  React.useEffect(() => {
    const loadPersistedState = async () => {
      try {
        const jsonString = await AsyncStorage.getItem(persistKey);
        if (jsonString != null) {
          setInitialState(JSON.parse(jsonString));
        }
        setIsReady(true);
      } catch (error) {
        captureException(`Failed to load state.`, error);
        setIsReady(true);
      }
    };
    loadPersistedState();
  }, [persistKey]);

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      {...others}
      key={persistKey}
      ref={forwardedRef}
      initialState={initialState}
      onStateChange={onStateChangeInternal}
    />
  );
}
Example #5
Source File: Card.tsx    From nlw2-proffy with MIT License 5 votes vote down vote up
private handleStartInteraction = () => {
    if (this.interactionHandle === undefined) {
      this.interactionHandle = InteractionManager.createInteractionHandle();
    }
  };
Example #6
Source File: Card.tsx    From nlw2-proffy with MIT License 5 votes vote down vote up
private handleEndInteraction = () => {
    if (this.interactionHandle !== undefined) {
      InteractionManager.clearInteractionHandle(this.interactionHandle);
      this.interactionHandle = undefined;
    }
  };
Example #7
Source File: Pager.tsx    From react-native-gallery-toolkit with MIT License 4 votes vote down vote up
Pager = typedMemo(function Pager<
  TPages,
  ItemT = UnpackItemT<TPages>,
>({
  pages,
  initialIndex,
  totalCount,
  numToRender = 2,
  onIndexChange = workletNoop,
  renderPage,
  width = dimensions.width,
  gutterWidth = GUTTER_WIDTH,
  shouldRenderGutter = true,
  keyExtractor,
  pagerWrapperStyles = {},
  getItem,
  springConfig,
  onPagerTranslateChange = workletNoop,
  onGesture = workletNoop,
  onEnabledGesture = workletNoop,
  shouldHandleGestureEvent = workletNoopTrue,
  initialDiffValue = 0,
  shouldUseInteractionManager = true,
  outerGestureHandlerRefs = [],
  verticallyEnabled = true,
}: PagerProps<TPages, ItemT>) {
  assertWorklet(onIndexChange);
  assertWorklet(onPagerTranslateChange);
  assertWorklet(onGesture);
  assertWorklet(onEnabledGesture);
  assertWorklet(shouldHandleGestureEvent);

  // make sure to not calculate translate with gutter
  // if we don't want to render it
  if (!shouldRenderGutter) {
    gutterWidth = 0;
  }

  const getPageTranslate = useWorkletCallback(
    (i: number) => {
      const t = i * width;
      const g = gutterWidth * i;
      return -(t + g);
    },
    [gutterWidth, width],
  );

  const pagerRef = useRef(null);
  const tapRef = useRef(null);

  const isActive = useSharedValue(true);

  const onPageStateChange = useWorkletCallback((value: boolean) => {
    isActive.value = value;
  }, []);

  const velocity = useSharedValue(0);
  const isMounted = useRef(false);

  const [diffValue, setDiffValue] = useState(initialDiffValue);
  useEffect(() => {
    if (shouldUseInteractionManager && !isMounted.current) {
      InteractionManager.runAfterInteractions(() => {
        setDiffValue(numToRender);
        console.log('numToRender in interaction');
      });
      isMounted.current = true;
    } else {
      setDiffValue(numToRender);
    }
  }, [numToRender]);

  // S2: Pager related stuff
  const [activeIndex, setActiveIndex] = useState(initialIndex);

  const index = useSharedValue(initialIndex);
  const length = useSharedValue(totalCount);
  const pagerX = useSharedValue(0);
  const toValueAnimation = useSharedValue(
    getPageTranslate(initialIndex),
  );

  const offsetX = useSharedValue(getPageTranslate(initialIndex));

  const totalWidth = useDerivedValue(() => {
    return length.value * width + gutterWidth * length.value - 2;
  }, []);

  const onIndexChangeCb = useWorkletCallback((nextIndex: number) => {
    onIndexChange(nextIndex);

    runOnJS(setActiveIndex)(nextIndex);
  }, []);

  const onIndexChangeWorklet = useWorkletCallback((i: number) => {
    offsetX.value = getPageTranslate(i);
    index.value = i;
    onIndexChangeCb(i);
  }, []);

  useEffect(() => {
    runOnUI(onIndexChangeWorklet)(initialIndex);
  }, [initialIndex]);

  const getSpringConfig = useWorkletCallback(
    (noVelocity?: boolean) => {
      const ratio = 1.1;
      const mass = 0.4;
      const stiffness = IS_ANDROID ? 200.0 : 100.0;
      const damping = ratio * 2.0 * Math.sqrt(mass * stiffness);

      const configToUse =
        typeof springConfig !== 'undefined'
          ? springConfig
          : {
              stiffness,
              mass,
              damping,
              restDisplacementThreshold: 1,
              restSpeedThreshold: 5,
            };

      // @ts-ignore
      // cannot use merge and spread here :(
      configToUse.velocity = noVelocity ? 0 : velocity.value;

      return configToUse;
    },
    [springConfig],
  );

  const onChangePageAnimation = useWorkletCallback(
    (noVelocity?: boolean) => {
      const config = getSpringConfig(noVelocity);

      if (offsetX.value === toValueAnimation.value) {
        return;
      }

      offsetX.value = withSpring(
        toValueAnimation.value,
        config,
        (isCanceled) => {
          if (!isCanceled) {
            velocity.value = 0;
          }
        },
      );
    },
    [getSpringConfig],
  );

  // S3 Pager
  const getCanSwipe = useWorkletCallback(
    (currentTranslate: number = 0) => {
      const nextTranslate = offsetX.value + currentTranslate;

      if (nextTranslate > 0) {
        return false;
      }

      const totalTranslate =
        width * (length.value - 1) + gutterWidth * (length.value - 1);

      if (Math.abs(nextTranslate) >= totalTranslate) {
        return false;
      }

      return true;
    },
    [width, gutterWidth],
  );

  const getNextIndex = useWorkletCallback(
    (v: number) => {
      const currentTranslate = Math.abs(
        getPageTranslate(index.value),
      );
      const currentIndex = index.value;
      const currentOffset = Math.abs(offsetX.value);

      const nextIndex = v < 0 ? currentIndex + 1 : currentIndex - 1;

      if (
        nextIndex < currentIndex &&
        currentOffset > currentTranslate
      ) {
        return currentIndex;
      }

      if (
        nextIndex > currentIndex &&
        currentOffset < currentTranslate
      ) {
        return currentIndex;
      }

      if (nextIndex > length.value - 1 || nextIndex < 0) {
        return currentIndex;
      }

      return nextIndex;
    },
    [getPageTranslate],
  );

  const isPagerInProgress = useDerivedValue(() => {
    return (
      Math.floor(Math.abs(getPageTranslate(index.value))) !==
      Math.floor(Math.abs(offsetX.value + pagerX.value))
    );
  }, [getPageTranslate]);

  const onPan = useAnimatedGestureHandler<
    PanGestureHandlerGestureEvent,
    {
      pagerActive: boolean;
      offsetX: null | number;
    }
  >({
    onGesture: (evt) => {
      onGesture(evt, isActive);

      if (isActive.value && !isPagerInProgress.value) {
        onEnabledGesture(evt);
      }
    },

    onInit: (_, ctx) => {
      ctx.offsetX = null;
    },

    shouldHandleEvent: (evt) => {
      return (
        (evt.numberOfPointers === 1 &&
          isActive.value &&
          Math.abs(evt.velocityX) > Math.abs(evt.velocityY) &&
          shouldHandleGestureEvent(evt)) ||
        isPagerInProgress.value
      );
    },

    onEvent: (evt) => {
      velocity.value = clampVelocity(
        evt.velocityX,
        MIN_VELOCITY,
        MAX_VELOCITY,
      );
    },

    onStart: (_, ctx) => {
      ctx.offsetX = null;
    },

    onActive: (evt, ctx) => {
      // workaround alert
      // the event triggers with a delay and first frame value jumps
      // we capture that value and subtract from the actual one
      // so the translate happens on a second frame
      if (ctx.offsetX === null) {
        ctx.offsetX =
          evt.translationX < 0 ? evt.translationX : -evt.translationX;
      }

      const val = evt.translationX - ctx.offsetX;

      const canSwipe = getCanSwipe(val);
      pagerX.value = canSwipe ? val : friction(val);
    },

    onEnd: (evt, ctx) => {
      const val = evt.translationX - ctx.offsetX!;

      const canSwipe = getCanSwipe(val);

      offsetX.value += pagerX.value;
      pagerX.value = 0;

      const nextIndex = getNextIndex(evt.velocityX);

      const vx = Math.abs(evt.velocityX);

      const translation = Math.abs(val);
      const isHalf = width / 2 < translation;

      const shouldMoveToNextPage = (vx > 10 || isHalf) && canSwipe;

      // we invert the value since the translationY is left to right
      toValueAnimation.value = -(shouldMoveToNextPage
        ? -getPageTranslate(nextIndex)
        : -getPageTranslate(index.value));

      onChangePageAnimation(!shouldMoveToNextPage);

      if (shouldMoveToNextPage) {
        index.value = nextIndex;
        onIndexChangeCb(nextIndex);
      }
    },
  });

  const onTap = useAnimatedGestureHandler({
    shouldHandleEvent: (evt) => {
      return evt.numberOfPointers === 1 && isActive.value;
    },

    onStart: () => {
      cancelAnimation(offsetX);
    },

    onEnd: () => {
      onChangePageAnimation(true);
    },
  });

  const pagerStyles = useAnimatedStyle<ViewStyle>(() => {
    const translateX = pagerX.value + offsetX.value;

    onPagerTranslateChange(translateX);

    return {
      width: totalWidth.value,
      transform: [
        {
          translateX,
        },
      ],
    };
  }, []);

  const pagerRefs = useMemo<PageRefs>(() => [pagerRef, tapRef], []);

  const pagesToRender = useMemo(() => {
    const temp = [];

    for (let i = 0; i < totalCount; i += 1) {
      let itemToUse;

      if (typeof getItem === 'function') {
        itemToUse = getItem(pages, i);
      } else if (Array.isArray(pages)) {
        itemToUse = pages[i];
      } else {
        throw new Error(
          'Pager: items either should be an array of getItem should be defined',
        );
      }

      const shouldRender = getShouldRender(i, activeIndex, diffValue);

      if (!shouldRender) {
        temp.push(null);
      } else {
        temp.push(
          <Page
            key={keyExtractor(itemToUse, i)}
            item={itemToUse}
            currentIndex={index}
            pagerRefs={pagerRefs}
            onPageStateChange={onPageStateChange}
            index={i}
            length={totalCount}
            gutterWidth={gutterWidth}
            renderPage={renderPage}
            getPageTranslate={getPageTranslate}
            width={width}
            isPagerInProgress={isPagerInProgress}
            shouldRenderGutter={shouldRenderGutter}
          />,
        );
      }
    }

    return temp;
  }, [
    activeIndex,
    diffValue,
    keyExtractor,
    getItem,
    totalCount,
    pages,
    getShouldRender,
    index,
    pagerRefs,
    onPageStateChange,
    gutterWidth,
    renderPage,
    getPageTranslate,
    width,
    isPagerInProgress,
    shouldRenderGutter,
  ]);

  return (
    <View style={StyleSheet.absoluteFillObject}>
      <Animated.View style={[StyleSheet.absoluteFill]}>
        <PanGestureHandler
          ref={pagerRef}
          minDist={0.1}
          minVelocityX={0.1}
          activeOffsetX={[-4, 4]}
          activeOffsetY={verticallyEnabled ? [-4, 4] : undefined}
          simultaneousHandlers={[tapRef, ...outerGestureHandlerRefs]}
          onGestureEvent={onPan}
        >
          <Animated.View style={StyleSheet.absoluteFill}>
            <TapGestureHandler
              ref={tapRef}
              maxDeltaX={10}
              maxDeltaY={10}
              simultaneousHandlers={pagerRef}
              onGestureEvent={onTap}
            >
              <Animated.View
                style={[StyleSheet.absoluteFill, pagerWrapperStyles]}
              >
                <Animated.View style={StyleSheet.absoluteFill}>
                  <Animated.View style={[styles.pager, pagerStyles]}>
                    {pagesToRender}
                  </Animated.View>
                </Animated.View>
              </Animated.View>
            </TapGestureHandler>
          </Animated.View>
        </PanGestureHandler>
      </Animated.View>
    </View>
  );
})