Search code examples
react-nativeanimationreact-animatedreact-native-reanimatedreact-native-gesture-handler

Native base Footer accepting touches for player in react native


I have a MainFooter components that contains the footer and mini player which animates to full-view when clicked. I have a problem that whenever we click on one of the footer Tabs, the player maximizes and then got stuck there only, being unresponsive.

Also the down arrow icon inside player when clicked do not minimizes it, neither clicking on MiniPlayer maximizes it, but we can maximizes MiniPlayer by clicking and dragging it to full-view and same for maximized Player.

Here is the MainFooter component:

export const MainFooter = ({ setCounter, counter }: Props) => {
  const translationY = new Value(0);
  const velocityY = new Value(0);
  const state = new Value(State.UNDETERMINED);
  const offset = new Value(SNAP_BOTTOM);
  const goUp: Animated.Value<0 | 1> = new Value(0);
  const goDown: Animated.Value<0 | 1> = new Value(0);

  const gestureHandler = onGestureEvent({
    state,
    translationY,
    velocityY,
  });

  const translateY = clamp(
    withSpring({
      state,
      value: translationY,
      velocity: velocityY,
      snapPoints: [SNAP_TOP, SNAP_BOTTOM],
      config,
    }),
    SNAP_TOP,
    SNAP_BOTTOM
  );

  const translateY_ = withSpring({
    value: clamp(translationY, SNAP_TOP, SNAP_BOTTOM),
    velocity: velocityY,
    offset,
    state,
    snapPoints: [SNAP_TOP, SNAP_BOTTOM],
    config,
  });

  const translateBottomTab = interpolate(translateY, {
    inputRange: [SNAP_BOTTOM - TABBAR_HEIGHT, SNAP_BOTTOM],
    outputRange: [TABBAR_HEIGHT, 0],
    extrapolate: Extrapolate.CLAMP,
  });

  const opacity = interpolate(translateY, {
    inputRange: [SNAP_BOTTOM - MINIMIZED_PLAYER_HEIGHT, SNAP_BOTTOM],
    outputRange: [0, 1],
    extrapolate: Extrapolate.CLAMP,
  });

  const opacity2 = interpolate(translateY, {
    inputRange: [
      SNAP_BOTTOM - MINIMIZED_PLAYER_HEIGHT * 2,
      SNAP_BOTTOM - MINIMIZED_PLAYER_HEIGHT,
    ],
    outputRange: [0, 1],
    extrapolate: Extrapolate.CLAMP,
  });

  return (
    <>
      <PanGestureHandler {...gestureHandler}>
        <Animated.View
          style={[
            styles.playerSheet,
            { transform: [{ translateY: translateY }] },
          ]}
        >
          <Player
            onPress={() => {
              goDown.setValue(1);
              console.log('player pressed!');
            }}
          />
          <Animated.View
            pointerEvents='none'
            style={{
              opacity: opacity2,
              backgroundColor: '#e0e0e0',
              ...StyleSheet.absoluteFillObject,
            }}
          />
          <Animated.View
            style={{
              opacity,
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0,
              height: MINIMIZED_PLAYER_HEIGHT,
            }}
          >
            <MiniPlayer
              onPress={() => {
                console.log('mini player pressed!');
                goUp.setValue(1);
              }}
            />
          </Animated.View>
        </Animated.View>
      </PanGestureHandler>
      <Animated.View
        style={{ transform: [{ translateY: translateBottomTab }] }}
      >
        <FooterTabsContainer counter={counter} setCounter={setCounter} />
      </Animated.View>
    </>
  );
};

const styles = StyleSheet.create({
  playerSheet: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: '#e0e0e0',
  },
  container: {
    // backgroundColor: '#e0e0e0',
    // position: 'absolute',
    // bottom: 0,
    // left: 0,
    // right: 0,
    // height: TABBAR_HEIGHT,
    // flexDirection: 'row',
    // borderTopColor: 'black',
    // borderWidth: 1,
    height: 'auto',
  },
});

And here is the FooterTabsContainer component:

const FooterTabsContainer = ({ counter, setCounter }: Props) => {
  const tintColor = {
    homeIcon: counter === 0 ? '#FFBC00' : '#555',
    playIcon: counter === 1 ? '#FFBC00' : '#555',
    loveIcon: counter === 2 ? '#FFBC00' : '#555',
    profileIcon: counter === 3 ? '#FFBC00' : '#555',
  };
  return (
    <>
      <Footer>
        <FooterTab style={styles.footerTab}>
          <Button onPress={() => setCounter(0)}>
            <Image
              source={homeIcon}
              style={{ ...styles.footerIcon, tintColor: tintColor.homeIcon }}
            />
          </Button>
          <Button onPress={() => setCounter(1)}>
            <Image
              source={playIcon}
              style={{ ...styles.footerIcon, tintColor: tintColor.playIcon }}
            />
          </Button>
          <Button onPress={() => setCounter(2)}>
            <Image
              source={loveIcon}
              style={{ ...styles.footerIcon, tintColor: tintColor.loveIcon }}
            />
          </Button>
          <Button
            onPress={() => {
              setCounter(3);
              console.log('profile icon clicked!');
            }}
          >
            <Image
              source={profileIcon}
              style={{ ...styles.footerIcon, tintColor: tintColor.profileIcon }}
            />
          </Button>
        </FooterTab>
      </Footer>
    </>
  );
};

export default FooterTabsContainer;

const styles = StyleSheet.create({
  footerTab: {
    backgroundColor: '#FFF',
    borderWidth: 2,
    borderColor: '#f0f0f0',
  },
  footerIcon: {
    width: 25,
    height: 25,
  },
});

I will appreciate any help or comments as to why this is happening in case of:

  1. Fixing issue of getting MiniPlayer maximized when clicked on Any of the footer's tab.
  2. Fixing issue of MiniPlayer not maximizing on clicked and full-view player not minimizing when clicked on down arrow icon

I am using "react-native-reanimated": "^1.13.2", "react-native-redash": "^8.0.0", "native-base": "^2.15.2", "react": "16.13.1", "react-native": "^0.64.0", and "react-native-gesture-handler": "^1.9.0"

Maximized Player

Maximized Player

Miniplayer:

Miniplayer


Solution

  • It was because storing animated values as new values, so whenever; migrated to another footer Tab, the states were lost due to re-render, and the player was coming back to it's original state (up). TO fix this wrap animation values inside useRef() and then use them:

      const translationY = useRef(new Value(0));
      const velocityY = useRef(new Value(0));
      const state = useRef(new Value(State.UNDETERMINED));
      const offset = new Value(SNAP_BOTTOM);
      const goUp: React.MutableRefObject<Animated.Value<0 | 1>> = useRef(
        new Value(0)
      );
      const goDown: React.MutableRefObject<Animated.Value<0 | 1>> = useRef(
        new Value(0)
      );
      .
      .
      .
      const translateY = useRef(
        clamp(
          withSpring({
            state: state.current,
            value: translationY.current,
            velocity: velocityY.current,
            offset,
            snapPoints: [SNAP_TOP, SNAP_BOTTOM],
            config,
          }),
          SNAP_TOP,
          SNAP_BOTTOM
        )
      );