react-native-reanimated#call TypeScript Examples

The following examples show how to use react-native-reanimated#call. 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: layoutUtil.ts    From react-native-template with MIT License 6 votes vote down vote up
useDebug = (values: { [key: string]: Animated.Node<number> }) => {
  const keys = Object.keys(values)
  const nodes = Object.values(values)

  useCode(
    () =>
      call(nodes, (arrayOfNodes) => {
        keys.map((key, i) => console.log(key + " " + arrayOfNodes[i]))
      }),
    [keys]
  )
}
Example #2
Source File: PaperOnboarding.tsx    From react-native-paper-onboarding with MIT License 4 votes vote down vote up
PaperOnboardingComponent = forwardRef<
  PaperOnboarding,
  PaperOnboardingProps
>(
  (
    {
      data,
      safeInsets: _safeInsets,
      direction = DEFAULT_DIRECTION,
      // indicator config
      indicatorSize = DEFAULT_INDICATOR_SIZE,
      indicatorBackgroundColor = DEFAULT_INDICATOR_BACKGROUND_COLOR,
      indicatorBorderColor = DEFAULT_INDICATOR_BORDER_COLOR,
      // override styles
      titleStyle,
      descriptionStyle,
      // close button config
      closeButton,
      closeButtonTextStyle,
      closeButtonText = DEFAULT_CLOSE_BUTTON_TEXT,
      onCloseButtonPress = DEFAULT_CLOSE_BUTTON_CALLBACK,
      onIndexChange,
    },
    ref
  ) => {
    // state
    const [dimensions, setDimensions] =
      useState<PaperOnboardingScreenDimensions>({
        width: Dimensions.get('window').width,
        height: Dimensions.get('window').height,
      });

    // refs
    const indexRef = useRef<number>(0);
    const pagesRef = useRef<Array<Animated.View | null>>(data.map(() => null));

    //#region variables
    const safeInsets = useMemo<Required<Insets>>(() => {
      return {
        top: _safeInsets?.top ?? DEFAULT_SAFE_INSET,
        bottom: _safeInsets?.bottom ?? DEFAULT_SAFE_INSET,
        left: _safeInsets?.left ?? DEFAULT_SAFE_INSET,
        right: _safeInsets?.right ?? DEFAULT_SAFE_INSET,
      };
    }, [_safeInsets]);
    const indicatorsContainerLeftPadding = useMemo(() => {
      const containerLeftPadding = dimensions.width / 2 - indicatorSize / 2;
      return I18nManager.isRTL
        ? -containerLeftPadding + indicatorSize * (data.length - 1)
        : containerLeftPadding;
    }, [dimensions.width, indicatorSize, data.length]);
    //#endregion

    //#region animated variables
    const { gestureHandler, state, translation, velocity } =
      usePanGestureHandler();
    const animatedStaticIndex = useValue<number>(0);
    const animatedOverrideIndex = useValue<number>(0);
    const animatedIndex = useTiming({
      animatedStaticIndex,
      animatedOverrideIndex,
      value: direction === 'horizontal' ? translation.x : translation.y,
      velocity: direction === 'horizontal' ? velocity.x : velocity.y,
      state: state,
      size: data.length,
      screenWidth: dimensions.width,
    });
    const indicatorsContainerPosition = useMemo(
      () => data.map((_, index) => index * indicatorSize * -1),
      [data, indicatorSize]
    );
    const animatedIndicatorsContainerPosition = useMemo(
      () =>
        add(
          interpolate(animatedIndex, {
            inputRange: data.map((_, index) => index),
            outputRange: I18nManager.isRTL
              ? indicatorsContainerPosition.reverse()
              : indicatorsContainerPosition,
            extrapolate: Animated.Extrapolate.CLAMP,
          }),
          indicatorsContainerLeftPadding
        ),
      [
        data,
        animatedIndex,
        indicatorsContainerLeftPadding,
        indicatorsContainerPosition,
      ]
    );
    //#endregion

    //#region callbacks
    const handlePageRef = useCallback((pageRef, index) => {
      pagesRef.current[index] = pageRef;
    }, []);

    const handleOnLayout = useCallback(
      ({
        nativeEvent: {
          layout: { width, height },
        },
      }: LayoutChangeEvent) => {
        setDimensions({
          width,
          height,
        });
      },
      []
    );
    //#endregion

    //#region public methods
    const handleNavigateToNextPage = useCallback(() => {
      const currentIndex = indexRef.current;
      if (currentIndex === data.length - 1) {
        return;
      }
      animatedOverrideIndex.setValue(currentIndex + 1);
    }, [data, animatedOverrideIndex]);
    const handleNavigateToPreviousPage = useCallback(() => {
      const currentIndex = indexRef.current;
      if (currentIndex === 0) {
        return;
      }
      animatedOverrideIndex.setValue(currentIndex - 1);
    }, [animatedOverrideIndex]);
    useImperativeHandle(
      ref,
      () => ({
        next: handleNavigateToNextPage,
        previous: handleNavigateToPreviousPage,
      }),
      [handleNavigateToNextPage, handleNavigateToPreviousPage]
    );
    //#endregion

    //#region effects
    useCode(
      () =>
        onChange(
          animatedStaticIndex,
          call([animatedStaticIndex], args => {
            indexRef.current = args[0];
            /**
             * @DEV
             * here we directly manipulate pages native props by setting `pointerEvents`
             * to `auto` for current page and `none` for others.
             */
            pagesRef.current.map((pageRef, _index) => {
              // @ts-ignore
              pageRef.setNativeProps({
                pointerEvents: _index === args[0] ? 'auto' : 'none',
              });
            });

            if (onIndexChange) {
              onIndexChange(args[0]);
            }
          })
        ),
      []
    );
    //#endregion

    // renders
    return (
      <PanGestureHandler {...gestureHandler}>
        <Animated.View onLayout={handleOnLayout} style={styles.container}>
          <Background
            animatedIndex={animatedIndex}
            data={data}
            safeInsets={safeInsets}
            screenDimensions={dimensions}
            indicatorSize={indicatorSize}
            animatedIndicatorsContainerPosition={
              animatedIndicatorsContainerPosition
            }
          />

          {data.map((item, index) => (
            <Page
              key={`page-${index}`}
              index={index}
              item={item}
              animatedIndex={animatedIndex}
              indicatorSize={indicatorSize}
              titleStyle={titleStyle}
              descriptionStyle={descriptionStyle}
              safeInsets={safeInsets}
              screenDimensions={dimensions}
              handleRef={handlePageRef}
            />
          ))}

          <IndicatorsContainer
            data={data}
            animatedIndex={animatedIndex}
            animatedIndicatorsContainerPosition={
              animatedIndicatorsContainerPosition
            }
            indicatorSize={indicatorSize}
            indicatorBackgroundColor={indicatorBackgroundColor}
            indicatorBorderColor={indicatorBorderColor}
            safeInsets={safeInsets}
          />

          <CloseButton
            data={data}
            animatedIndex={animatedIndex}
            safeInsets={safeInsets}
            closeButtonText={closeButtonText}
            closeButtonTextStyle={closeButtonTextStyle}
            closeButton={closeButton}
            onCloseButtonPress={onCloseButtonPress}
          />
        </Animated.View>
      </PanGestureHandler>
    );
  }
)
Example #3
Source File: StickyItemFlatList.tsx    From react-native-sticky-item with MIT License 4 votes vote down vote up
StickyItemFlatList = forwardRef(
  <T extends {}>(props: StickyItemFlatListProps<T>, ref: Ref<FlatList<T>>) => {
    const {
      initialScrollIndex = 0,
      decelerationRate = DEFAULT_DECELERATION_RATE,
      itemWidth,
      itemHeight,
      separatorSize = DEFAULT_SEPARATOR_SIZE,
      borderRadius = DEFAULT_BORDER_RADIUS,
      stickyItemActiveOpacity = DEFAULT_STICKY_ITEM_ACTIVE_OPACITY,
      stickyItemWidth,
      stickyItemHeight,
      stickyItemBackgroundColors,
      stickyItemContent,
      onStickyItemPress,
      isRTL = DEFAULT_IS_RTL,
      ItemSeparatorComponent = Separator,
      ...rest
    } = props;

    // refs
    const flatListRef = useRef<FlatList<T>>(null);
    const tapRef = useRef<TapGestureHandler>(null);

    //#region variables
    const itemWidthWithSeparator = useMemo(
      () => itemWidth + separatorSize,
      [itemWidth, separatorSize]
    );
    const separatorProps = useMemo(
      () => ({
        size: separatorSize,
      }),
      [separatorSize]
    );
    //#endregion

    //#region styles
    const contentContainerStyle = useMemo(
      () => [
        rest.contentContainerStyle,
        {
          paddingLeft: itemWidth + separatorSize * 2,
          paddingRight: separatorSize,
        },
      ],
      [rest.contentContainerStyle, itemWidth, separatorSize]
    );
    //#endregion

    //#region methods
    const getHitSlop = useCallback(
      isMinimized => {
        const verticalPosition = isMinimized
          ? -((itemHeight - stickyItemHeight) / 2)
          : 0;
        const startPosition = isMinimized ? 0 : -separatorSize;
        const endPosition = isMinimized
          ? -(SCREEN_WIDTH - stickyItemWidth)
          : -(SCREEN_WIDTH - separatorSize - itemWidth);

        return {
          top: verticalPosition,
          right: isRTL ? startPosition : endPosition,
          left: isRTL ? endPosition : startPosition,
          bottom: verticalPosition,
        };
      },
      [
        itemWidth,
        itemHeight,
        stickyItemWidth,
        stickyItemHeight,
        separatorSize,
        isRTL,
      ]
    );
    const getItemLayout = useCallback(
      (_, index) => {
        return {
          length: itemWidthWithSeparator,
          // sticky item + previous items width
          offset: itemWidthWithSeparator + itemWidthWithSeparator * index,
          index,
        };
      },
      [itemWidthWithSeparator]
    );
    //#endregion

    //#region gesture
    const x = useValue(0);
    const tapState = useValue(State.UNDETERMINED);
    const tapGestures = useGestureHandler({ state: tapState });
    const onScroll = event([
      {
        nativeEvent: {
          contentOffset: {
            x,
          },
        },
      },
    ]);
    const onScrollEnd = event([
      {
        nativeEvent: {
          contentOffset: {
            x,
          },
        },
      },
    ]);
    //#endregion

    //#region effects
    //@ts-ignore
    useImperativeHandle(ref, () => flatListRef.current!.getNode());
    useCode(
      () =>
        cond(eq(tapState, State.END), [
          call([tapState], () => {
            if (onStickyItemPress) {
              onStickyItemPress();
            }
          }),
          set(tapState, State.UNDETERMINED),
        ]),
      [tapState]
    );
    useCode(
      () =>
        onChange(
          x,
          call([x], args => {
            if (tapRef.current) {
              const isMinimized = args[0] > 0;
              // @ts-ignore
              tapRef.current.setNativeProps({
                hitSlop: getHitSlop(isMinimized),
              });
            }
          })
        ),
      [
        x,
        itemWidth,
        itemHeight,
        stickyItemWidth,
        stickyItemWidth,
        separatorSize,
      ]
    );
    useEffect(() => {
      /**
       * @DEV
       * to fix stick item position with fast refresh
       */
      x.setValue(0);

      if (tapRef.current) {
        // @ts-ignore
        tapRef.current.setNativeProps({
          hitSlop: getHitSlop(initialScrollIndex !== 0),
        });
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [getHitSlop]);
    //#endregion

    // render
    const renderSeparator = useCallback(() => {
      if (typeof ItemSeparatorComponent === 'function') {
        // @ts-ignore
        return ItemSeparatorComponent(separatorProps);
      } else {
        // @ts-ignore
        return <ItemSeparatorComponent size={separatorProps.size} />;
      }
    }, [ItemSeparatorComponent, separatorProps]);

    return (
      <TapGestureHandler
        ref={tapRef}
        waitFor={flatListRef}
        shouldCancelWhenOutside={true}
        {...tapGestures}
      >
        <Animated.View>
          <AnimatedFlatList
            {...rest}
            ref={flatListRef}
            initialScrollIndex={initialScrollIndex}
            inverted={isRTL}
            ItemSeparatorComponent={renderSeparator}
            contentContainerStyle={contentContainerStyle}
            horizontal={true}
            showsHorizontalScrollIndicator={false}
            scrollEventThrottle={1}
            pagingEnabled={true}
            decelerationRate={decelerationRate}
            snapToAlignment={'start'}
            snapToInterval={itemWidth + separatorSize}
            onScroll={onScroll}
            onScrollAnimationEnd={onScrollEnd}
            getItemLayout={getItemLayout}
          />
          <StickyItem
            x={x}
            tapState={tapState}
            itemWidth={itemWidth}
            itemHeight={itemHeight}
            separatorSize={separatorSize}
            borderRadius={borderRadius}
            stickyItemActiveOpacity={stickyItemActiveOpacity}
            stickyItemWidth={stickyItemWidth}
            stickyItemHeight={stickyItemHeight}
            stickyItemBackgroundColors={stickyItemBackgroundColors}
            stickyItemContent={stickyItemContent}
            isRTL={isRTL}
          />
        </Animated.View>
      </TapGestureHandler>
    );
  }
)
Example #4
Source File: index.tsx    From react-native-scroll-bottom-sheet with MIT License 4 votes vote down vote up
render() {
    const {
      renderHandle,
      snapPoints,
      initialSnapIndex,
      componentType,
      onSettle,
      animatedPosition,
      containerStyle,
      ...rest
    } = this.props;
    const AnimatedScrollableComponent = this.scrollComponent;
    const normalisedSnapPoints = this.getNormalisedSnapPoints();
    const initialSnap = normalisedSnapPoints[initialSnapIndex];

    const Content = (
      <Animated.View
        style={[
          StyleSheet.absoluteFillObject,
          containerStyle,
          // @ts-ignore
          {
            transform: [{ translateY: this.translateY }],
          },
        ]}
      >
        <PanGestureHandler
          ref={this.drawerHandleRef}
          shouldCancelWhenOutside={false}
          simultaneousHandlers={this.masterDrawer}
          onGestureEvent={this.onHandleGestureEvent}
          onHandlerStateChange={this.onHandleGestureEvent}
        >
          <Animated.View>{renderHandle()}</Animated.View>
        </PanGestureHandler>
        <PanGestureHandler
          ref={this.drawerContentRef}
          simultaneousHandlers={[this.scrollComponentRef, this.masterDrawer]}
          shouldCancelWhenOutside={false}
          onGestureEvent={this.onDrawerGestureEvent}
          onHandlerStateChange={this.onDrawerGestureEvent}
        >
          <Animated.View style={styles.container}>
            <NativeViewGestureHandler
              ref={this.scrollComponentRef}
              waitFor={this.masterDrawer}
              simultaneousHandlers={this.drawerContentRef}
            >
              <AnimatedScrollableComponent
                overScrollMode="never"
                bounces={false}
                {...rest}
                ref={this.props.innerRef}
                // @ts-ignore
                decelerationRate={this.decelerationRate}
                onScrollBeginDrag={this.onScrollBeginDrag}
                scrollEventThrottle={1}
                contentContainerStyle={[
                  rest.contentContainerStyle,
                  { paddingBottom: this.getNormalisedSnapPoints()[0] },
                ]}
              />
            </NativeViewGestureHandler>
          </Animated.View>
        </PanGestureHandler>
        {this.props.animatedPosition && (
          <Animated.Code
            exec={onChange(
              this.position,
              set(this.props.animatedPosition, this.position)
            )}
          />
        )}
        <Animated.Code
          exec={onChange(
            this.dragY,
            cond(not(eq(this.dragY, 0)), set(this.prevDragY, this.dragY))
          )}
        />
        <Animated.Code
          exec={onChange(
            this.didGestureFinish,
            cond(this.didGestureFinish, [
              this.didScrollUpAndPullDown,
              this.setTranslationY,
              set(
                this.tempDestSnapPoint,
                add(normalisedSnapPoints[0], this.extraOffset)
              ),
              set(this.nextSnapIndex, 0),
              set(this.destSnapPoint, this.calculateNextSnapPoint()),
              cond(
                and(
                  greaterThan(this.dragY, this.lastStartScrollY),
                  this.isAndroid,
                  not(this.dragWithHandle)
                ),
                call([], () => {
                  // This prevents the scroll glide from happening on Android when pulling down with inertia.
                  // It's not perfect, but does the job for now
                  const { method, args } = imperativeScrollOptions[
                    this.props.componentType
                  ];
                  // @ts-ignore
                  const node = this.props.innerRef.current?.getNode();

                  if (
                    node &&
                    node[method] &&
                    ((this.props.componentType === 'FlatList' &&
                      (this.props?.data?.length || 0) > 0) ||
                      (this.props.componentType === 'SectionList' &&
                        this.props.sections.length > 0) ||
                      this.props.componentType === 'ScrollView')
                  ) {
                    node[method](args);
                  }
                })
              ),
              set(this.dragY, 0),
              set(this.velocityY, 0),
              set(
                this.lastSnap,
                sub(
                  this.destSnapPoint,
                  cond(
                    eq(this.scrollUpAndPullDown, 1),
                    this.lastStartScrollY,
                    0
                  )
                )
              ),
              call([this.lastSnap], ([value]) => {
                // This is the TapGHandler trick
                // @ts-ignore
                this.masterDrawer?.current?.setNativeProps({
                  maxDeltaY: value - this.getNormalisedSnapPoints()[0],
                });
              }),
              set(
                this.decelerationRate,
                cond(
                  eq(this.isAndroid, 1),
                  cond(
                    eq(this.lastSnap, normalisedSnapPoints[0]),
                    ANDROID_NORMAL_DECELERATION_RATE,
                    0
                  ),
                  IOS_NORMAL_DECELERATION_RATE
                )
              ),
            ])
          )}
        />
        <Animated.Code
          exec={onChange(this.isManuallySetValue, [
            cond(
              this.isManuallySetValue,
              [
                set(this.destSnapPoint, this.manualYOffset),
                set(this.animationFinished, 0),
                set(this.lastSnap, this.manualYOffset),
                call([this.lastSnap], ([value]) => {
                  // This is the TapGHandler trick
                  // @ts-ignore
                  this.masterDrawer?.current?.setNativeProps({
                    maxDeltaY: value - this.getNormalisedSnapPoints()[0],
                  });
                }),
              ],
              [set(this.nextSnapIndex, 0)]
            ),
          ])}
        />
      </Animated.View>
    );

    // On Android, having an intermediary view with pointerEvents="box-none", breaks the
    // waitFor logic
    if (Platform.OS === 'android') {
      return (
        <TapGestureHandler
          maxDurationMs={100000}
          ref={this.masterDrawer}
          maxDeltaY={initialSnap - this.getNormalisedSnapPoints()[0]}
          shouldCancelWhenOutside={false}
        >
          {Content}
        </TapGestureHandler>
      );
    }

    // On iOS, We need to wrap the content on a view with PointerEvents box-none
    // So that we can start scrolling automatically when reaching the top without
    // Stopping the gesture
    return (
      <TapGestureHandler
        maxDurationMs={100000}
        ref={this.masterDrawer}
        maxDeltaY={initialSnap - this.getNormalisedSnapPoints()[0]}
      >
        <View style={StyleSheet.absoluteFillObject} pointerEvents="box-none">
          {Content}
        </View>
      </TapGestureHandler>
    );
  }
Example #5
Source File: index.tsx    From react-native-scroll-bottom-sheet with MIT License 4 votes vote down vote up
constructor(props: Props<T>) {
    super(props);
    const { initialSnapIndex, animationType } = props;

    const animationDriver = animationType === 'timing' ? 0 : 1;
    const animationDuration =
      (props.animationType === 'timing' && props.animationConfig?.duration) ||
      DEFAULT_ANIMATION_DURATION;

    const ScrollComponent = this.getScrollComponent();
    // @ts-ignore
    this.scrollComponent = Animated.createAnimatedComponent(ScrollComponent);

    const snapPoints = this.getNormalisedSnapPoints();
    const openPosition = snapPoints[0];
    const closedPosition = this.props.enableOverScroll
      ? windowHeight
      : snapPoints[snapPoints.length - 1];
    const initialSnap = snapPoints[initialSnapIndex];
    this.nextSnapIndex = new Value(initialSnapIndex);

    const initialDecelerationRate = Platform.select({
      android:
        props.initialSnapIndex === 0 ? ANDROID_NORMAL_DECELERATION_RATE : 0,
      ios: IOS_NORMAL_DECELERATION_RATE,
    });
    this.decelerationRate = new Value(initialDecelerationRate);

    const handleGestureState = new Value<GestureState>(-1);
    const handleOldGestureState = new Value<GestureState>(-1);
    const drawerGestureState = new Value<GestureState>(-1);
    const drawerOldGestureState = new Value<GestureState>(-1);

    const lastSnapInRange = new Value(1);
    this.prevTranslateYOffset = new Value(initialSnap);
    this.translationY = new Value(initialSnap);

    this.lastSnap = new Value(initialSnap);

    this.onHandleGestureEvent = event([
      {
        nativeEvent: {
          translationY: this.dragY,
          oldState: handleOldGestureState,
          state: handleGestureState,
          velocityY: this.velocityY,
        },
      },
    ]);
    this.onDrawerGestureEvent = event([
      {
        nativeEvent: {
          translationY: this.dragY,
          oldState: drawerOldGestureState,
          state: drawerGestureState,
          velocityY: this.velocityY,
        },
      },
    ]);
    this.onScrollBeginDrag = event([
      {
        nativeEvent: {
          contentOffset: { y: this.lastStartScrollY },
        },
      },
    ]);

    const didHandleGestureBegin = eq(handleGestureState, GestureState.ACTIVE);

    const isAnimationInterrupted = and(
      or(
        eq(handleGestureState, GestureState.BEGAN),
        eq(drawerGestureState, GestureState.BEGAN),
        and(
          eq(this.isAndroid, 0),
          eq(animationDriver, 1),
          or(
            eq(drawerGestureState, GestureState.ACTIVE),
            eq(handleGestureState, GestureState.ACTIVE)
          )
        )
      ),
      clockRunning(this.animationClock)
    );

    this.didGestureFinish = or(
      and(
        eq(handleOldGestureState, GestureState.ACTIVE),
        eq(handleGestureState, GestureState.END)
      ),
      and(
        eq(drawerOldGestureState, GestureState.ACTIVE),
        eq(drawerGestureState, GestureState.END)
      )
    );

    // Function that determines if the last snap point is in the range {snapPoints}
    // In the case of interruptions in the middle of an animation, we'll get
    // lastSnap values outside the range
    const isLastSnapPointInRange = (i: number = 0): Animated.Node<number> =>
      i === snapPoints.length
        ? lastSnapInRange
        : cond(
            eq(this.lastSnap, snapPoints[i]),
            [set(lastSnapInRange, 1)],
            isLastSnapPointInRange(i + 1)
          );

    const scrollY = [
      set(lastSnapInRange, 0),
      isLastSnapPointInRange(),
      cond(
        or(
          didHandleGestureBegin,
          and(
            this.isManuallySetValue,
            not(eq(this.manualYOffset, snapPoints[0]))
          )
        ),
        [set(this.dragWithHandle, 1), 0]
      ),
      cond(
        // This is to account for a continuous scroll on the drawer from a snap point
        // Different than top, bringing the drawer to the top position, so that if we
        // change scroll direction without releasing the gesture, it doesn't pull down the drawer again
        and(
          eq(this.dragWithHandle, 1),
          greaterThan(snapPoints[0], add(this.lastSnap, this.dragY)),
          and(not(eq(this.lastSnap, snapPoints[0])), lastSnapInRange)
        ),
        [
          set(this.lastSnap, snapPoints[0]),
          set(this.dragWithHandle, 0),
          this.lastStartScrollY,
        ],
        cond(eq(this.dragWithHandle, 1), 0, this.lastStartScrollY)
      ),
    ];

    this.didScrollUpAndPullDown = cond(
      and(
        greaterOrEq(this.dragY, this.lastStartScrollY),
        greaterThan(this.lastStartScrollY, 0)
      ),
      set(this.scrollUpAndPullDown, 1)
    );

    this.setTranslationY = cond(
      and(
        not(this.dragWithHandle),
        not(greaterOrEq(this.dragY, this.lastStartScrollY))
      ),
      set(this.translationY, sub(this.dragY, this.lastStartScrollY)),
      set(this.translationY, this.dragY)
    );

    this.extraOffset = cond(
      eq(this.scrollUpAndPullDown, 1),
      this.lastStartScrollY,
      0
    );
    const endOffsetY = add(
      this.lastSnap,
      this.translationY,
      multiply(1 - props.friction, this.velocityY)
    );

    this.calculateNextSnapPoint = (i = 0): Animated.Node<number> | number =>
      i === snapPoints.length
        ? this.tempDestSnapPoint
        : cond(
            greaterThan(
              abs(sub(this.tempDestSnapPoint, endOffsetY)),
              abs(sub(add(snapPoints[i], this.extraOffset), endOffsetY))
            ),
            [
              set(this.tempDestSnapPoint, add(snapPoints[i], this.extraOffset)),
              set(this.nextSnapIndex, i),
              this.calculateNextSnapPoint(i + 1),
            ],
            this.calculateNextSnapPoint(i + 1)
          );

    const runAnimation = ({
      clock,
      from,
      to,
      position,
      finished,
      velocity,
      frameTime,
    }: TimingParams) => {
      const state = {
        finished,
        velocity: new Value(0),
        position,
        time: new Value(0),
        frameTime,
      };

      const timingConfig = {
        duration: animationDuration,
        easing:
          (props.animationType === 'timing' && props.animationConfig?.easing) ||
          DEFAULT_EASING,
        toValue: new Value(0),
      };

      const springConfig = {
        ...DEFAULT_SPRING_PARAMS,
        ...((props.animationType === 'spring' && props.animationConfig) || {}),
        toValue: new Value(0),
      };

      return [
        cond(and(not(clockRunning(clock)), not(eq(finished, 1))), [
          // If the clock isn't running, we reset all the animation params and start the clock
          set(state.finished, 0),
          set(state.velocity, velocity),
          set(state.time, 0),
          set(state.position, from),
          set(state.frameTime, 0),
          set(timingConfig.toValue, to),
          set(springConfig.toValue, to),
          startClock(clock),
        ]),
        // We run the step here that is going to update position
        cond(
          eq(animationDriver, 0),
          timing(clock, state, timingConfig),
          spring(clock, state, springConfig)
        ),
        cond(
          state.finished,
          [
            call([this.nextSnapIndex], ([value]) => {
              if (value !== this.prevSnapIndex) {
                this.props.onSettle?.(value);
              }
              this.prevSnapIndex = value;
            }),
            // Resetting appropriate values
            set(drawerOldGestureState, GestureState.END),
            set(handleOldGestureState, GestureState.END),
            set(this.prevTranslateYOffset, state.position),
            cond(eq(this.scrollUpAndPullDown, 1), [
              set(
                this.prevTranslateYOffset,
                sub(this.prevTranslateYOffset, this.lastStartScrollY)
              ),
              set(this.lastStartScrollY, 0),
              set(this.scrollUpAndPullDown, 0),
            ]),
            cond(eq(this.destSnapPoint, snapPoints[0]), [
              set(this.dragWithHandle, 0),
            ]),
            set(this.isManuallySetValue, 0),
            set(this.manualYOffset, 0),
            stopClock(clock),
            this.prevTranslateYOffset,
          ],
          // We made the block return the updated position,
          state.position
        ),
      ];
    };

    const translateYOffset = cond(
      isAnimationInterrupted,
      [
        // set(prevTranslateYOffset, animationPosition) should only run if we are
        // interrupting an animation when the drawer is currently in a different
        // position than the top
        cond(
          or(
            this.dragWithHandle,
            greaterOrEq(abs(this.prevDragY), this.lastStartScrollY)
          ),
          set(this.prevTranslateYOffset, this.animationPosition)
        ),
        set(this.animationFinished, 1),
        set(this.translationY, 0),
        // Resetting appropriate values
        set(drawerOldGestureState, GestureState.END),
        set(handleOldGestureState, GestureState.END),
        // By forcing that frameTime exceeds duration, it has the effect of stopping the animation
        set(this.animationFrameTime, add(animationDuration, 1000)),
        set(this.velocityY, 0),
        stopClock(this.animationClock),
        this.prevTranslateYOffset,
      ],
      cond(
        or(
          this.didGestureFinish,
          this.isManuallySetValue,
          clockRunning(this.animationClock)
        ),
        [
          runAnimation({
            clock: this.animationClock,
            from: cond(
              this.isManuallySetValue,
              this.prevTranslateYOffset,
              add(this.prevTranslateYOffset, this.translationY)
            ),
            to: this.destSnapPoint,
            position: this.animationPosition,
            finished: this.animationFinished,
            frameTime: this.animationFrameTime,
            velocity: this.velocityY,
          }),
        ],
        [
          set(this.animationFrameTime, 0),
          set(this.animationFinished, 0),
          // @ts-ignore
          this.prevTranslateYOffset,
        ]
      )
    );

    this.translateY = interpolate(
      add(translateYOffset, this.dragY, multiply(scrollY, -1)),
      {
        inputRange: [openPosition, closedPosition],
        outputRange: [openPosition, closedPosition],
        extrapolate: Extrapolate.CLAMP,
      }
    );

    this.position = interpolate(this.translateY, {
      inputRange: [openPosition, snapPoints[snapPoints.length - 1]],
      outputRange: [1, 0],
      extrapolate: Extrapolate.CLAMP,
    });
  }