Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Commit

Permalink
feat: add double tap control
Browse files Browse the repository at this point in the history
  • Loading branch information
alantoa committed Feb 1, 2022
1 parent 6344b39 commit b5e9163
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 13 deletions.
3 changes: 1 addition & 2 deletions example/src/screens/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
} from 'react-native-safe-area-context';
import VideoPlayer from 'react-native-video-player';
import { Text } from './../../src/components';
import { width } from '../../../src/utils';

import type { RootParamList, CustomTheme } from '../../App';
import { palette } from '../theme/palette';

export const Home = () => {
const navigate = useNavigation<NativeStackNavigationProp<RootParamList>>();
const progress = useSharedValue(0);
Expand Down
139 changes: 139 additions & 0 deletions src/components/ripple-btn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from 'react';
import type { ViewStyle } from 'react-native';
import { StyleSheet, View } from 'react-native';
import type { TapGestureHandlerEventPayload } from 'react-native-gesture-handler';
import type { GestureEvent } from 'react-native-gesture-handler';
import {
State,
TapGestureHandler,
} from 'react-native-gesture-handler';
import Animated, {
Easing,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { hexToRgbA } from '../utils/hexToRgba';

type ValueOf<T> = T[keyof T];

type RippleBtnProps = {
children: React.ReactElement;
color: string;
onPress?: () => void;
rippleScale?: number,
duration?: number,
overflow?: boolean,
rippleColor?: string,
rippleOpacity?: number,
containerStyle?: ViewStyle
}
type BtnRefs = {

}
const _RippleBtn = React.forwardRef<BtnRefs, RippleBtnProps>(({
children,
color,
containerStyle,
onPress = () => { },
rippleScale = 1,
duration = 250,
overflow = false,
rippleColor = '#000',
rippleOpacity = 0.5,
}, ref) => {
const [radius, setRadius] = React.useState(-1);
const child = React.Children.only(children);
const scale = useSharedValue(0);
const positionX = useSharedValue(0);
const positionY = useSharedValue(0);

const isFinished = useSharedValue(false);
const uas = useAnimatedStyle(() => ({
top: positionY.value - radius,
left: positionX.value - radius,
transform: [{
scale: scale.value,
}],
}), [radius]);
const doubleTapHandler = useAnimatedGestureHandler<
GestureEvent<TapGestureHandlerEventPayload>
>({
onStart: ({ numberOfPointers }) => {
if (numberOfPointers !== 1) return;
isFinished.value = false;
},
onActive: ({ x, y, numberOfPointers, state, }) => {
if (numberOfPointers !== 1) return;
if (state === State.FAILED) {
scale.value = 0
} else {
positionX.value = x;
positionY.value = y;
scale.value = withTiming(
rippleScale,
{ duration, easing: Easing.bezier(0, 0, 0.8, 0.4) },
(finised) => {
if (finised) {
isFinished.value = true;
scale.value = withTiming(0, { duration: 0 });
}

},
)
}





},
onEnd: () => {
if (isFinished.value) {
scale.value = withTiming(0, { duration: 0 });
}
if (onPress) {
runOnJS(onPress)
}
}
});
return (
<TapGestureHandler
maxDurationMs={500}
maxDeltaX={10}
numberOfTaps={2}
ref={ref}
onGestureEvent={doubleTapHandler}
>
<Animated.View {...child.props} style={child.props.style}>
<View
style={[{
...StyleSheet.absoluteFillObject,
backgroundColor: color,
overflow: !overflow ? 'hidden' : undefined,
}, containerStyle]}
onLayout={({ nativeEvent: { layout: { width, height } } }) => {
setRadius(Math.sqrt(width ** 2 + height ** 2));
}}
>
{radius !== -1 && (
<Animated.View style={[uas, {
position: 'absolute',
width: radius * 2,
height: radius * 2,
borderRadius: radius,
backgroundColor: hexToRgbA(rippleColor, rippleOpacity),
}]}
/>
)}
</View>
{child.props.children}
</Animated.View>
</TapGestureHandler>
);
}

)
export const RippleBtn = React.memo(_RippleBtn)
101 changes: 90 additions & 11 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import LottieView from 'lottie-react-native';
import React, { useEffect, useRef, useState } from 'react';
import { StatusBar, StyleSheet, View } from 'react-native';
import { StatusBar, StyleSheet, View, useWindowDimensions } from 'react-native';
import { Slider } from 'react-native-awesome-slider/src/index';
import {
GestureEvent,
TapGestureHandler,
PanGestureHandler,
PanGestureHandlerEventPayload,
RectButton
} from 'react-native-gesture-handler';

import Orientation, { OrientationType } from 'react-native-orientation-locker';
import Animated, {
cancelAnimation,
Expand All @@ -27,13 +29,13 @@ import Video, {
} from 'react-native-video';
import { VideoLoader } from './video-loading';
import { secondToTime, formatTimeToMins, formatTime } from './video-utils';
import { bin, isIos, useVector } from './utils';
import { bin, isIos, useRefs, useVector } from './utils';
import { Dimensions } from 'react-native';
import { palette } from './theme/palette';
import { Text } from './components';
import { Image } from 'react-native';
import { TapControler } from './tap-controler';

import { RippleBtn } from './components/ripple-btn';
export const { width, height, scale, fontScale } = Dimensions.get('window');

const VIDEO_DEFAULT_HEIGHT = width * (9 / 16);
Expand Down Expand Up @@ -64,6 +66,7 @@ interface IProps extends VideoProperties {
autoPlay?: boolean;
onToggleAutoPlay?: (state: boolean) => void;
onTapMore?: () => void;
doubleTapInterval?: number
}
const VideoPlayer: React.FC<IProps> = ({
resizeMode = 'contain',
Expand All @@ -90,14 +93,16 @@ const VideoPlayer: React.FC<IProps> = ({
autoPlay = false,
onToggleAutoPlay,
onTapMore,
doubleTapInterval = 500,
...rest
}) => {
/**
* hooks
*/
const insets = useSafeAreaInsets();
const insetsRef = useRef(insets);

const dimensions = useWindowDimensions()
const doubleTapWidth = (dimensions.width / 2) - insets.left - insets.right - insets.top
/**
* state
*/
Expand All @@ -109,7 +114,6 @@ const VideoPlayer: React.FC<IProps> = ({
volume: volume,
rate: rate,
// Controls

showHours: showHours,
error: false,
showRemainingTime: false,
Expand All @@ -128,7 +132,7 @@ const VideoPlayer: React.FC<IProps> = ({
const videoPlayer = useRef<Video>(null);
const mounted = useRef(false);
const autoPlayAnimation = useSharedValue(autoPlay ? 1 : 0);

const { tap, doubleTapLeft, doubleTapRight, pan } = useRefs();
/**
* reanimated value
*/
Expand Down Expand Up @@ -300,7 +304,10 @@ const VideoPlayer: React.FC<IProps> = ({
controlViewOpacity.value = 0;
}
};
const doubleTapHandler = () => {
console.log(21312);

};
const onPauseTapHandler = () => {
if (controlViewOpacity.value === 0) {
showControlAnimation();
Expand Down Expand Up @@ -354,9 +361,9 @@ const VideoPlayer: React.FC<IProps> = ({
return showTimeRemaining
? `${formatTimeToMins(currentTime)}`
: `-${formatTime({
time: player.current.duration - currentTime,
duration: player.current.duration,
})}`;
time: player.current.duration - currentTime,
duration: player.current.duration,
})}`;
};
/**
* Seek to a time in the video.
Expand Down Expand Up @@ -517,7 +524,7 @@ const VideoPlayer: React.FC<IProps> = ({

return (
<>
<PanGestureHandler onGestureEvent={onPanGesture}>
<PanGestureHandler ref={pan} onGestureEvent={onPanGesture}>
<Animated.View style={styles.viewContainer}>
<Animated.View style={[styles.view, videoStyle]}>
<Video
Expand All @@ -538,12 +545,38 @@ const VideoPlayer: React.FC<IProps> = ({
fullscreenAutorotate={true}
/>
<VideoLoader loading={loading} />

<TapGestureHandler
ref={tap}
waitFor={[doubleTapLeft, doubleTapRight]}
onActivated={singleTapHandler}
maxDeltaX={10}
maxDeltaY={10}>
<Animated.View style={StyleSheet.absoluteFillObject}>

<Animated.View style={[styles.controlView, controlViewStyles]}>
{/* <TapGestureHandler
ref={doubleTapLeft}
onActivated={doubleTapHandler}
maxDeltaX={10}
numberOfTaps={2}
maxDurationMs={doubleTapInterval}
maxDeltaY={10}>
<Animated.View style={[controlStyle.doubleTap, controlStyle.leftDoubleTap, { width: doubleTapWidth }]}>
</Animated.View>
</TapGestureHandler> */}

<TapGestureHandler
ref={doubleTapRight}
onActivated={doubleTapHandler}
maxDeltaX={10}
numberOfTaps={2}
maxDurationMs={doubleTapInterval}
maxDeltaY={10}>
<Animated.View style={[controlStyle.doubleTap, controlStyle.rightDoubleTap, { width: doubleTapWidth }]}>
</Animated.View>
</TapGestureHandler>
<Animated.View
hitSlop={hitSlop}
style={[
Expand Down Expand Up @@ -710,7 +743,31 @@ const VideoPlayer: React.FC<IProps> = ({
</Animated.View>
</Animated.View>
</Animated.View>

<RippleBtn
color={'transparent'}
rippleColor="#fff"
rippleOpacity={0.1}
ref={doubleTapLeft}
containerStyle={controlStyle.leftDoubleTapContainer}

>
<Animated.View style={[controlStyle.doubleTap, controlStyle.leftDoubleTap, { width: doubleTapWidth }]}>
</Animated.View>
</RippleBtn>

<RippleBtn
color={'transparent'}
rippleColor="#fff"
rippleOpacity={0.1}
ref={doubleTapRight}
containerStyle={controlStyle.rightDoubleTapContainer}
>
<Animated.View style={[controlStyle.doubleTap, controlStyle.rightDoubleTap, { width: doubleTapWidth }]}>
</Animated.View>
</RippleBtn>
</Animated.View>

</TapGestureHandler>
<Animated.View style={[styles.slider, bottomSliderStyle]}>
<Slider
Expand Down Expand Up @@ -742,7 +799,7 @@ const VideoPlayer: React.FC<IProps> = ({
)}
</Animated.View>
</Animated.View>
</PanGestureHandler>
</PanGestureHandler >
<Animated.View
style={[styles.backdrop, backdropStyles]}
pointerEvents={'none'}
Expand All @@ -766,6 +823,7 @@ const styles = StyleSheet.create({
controlView: {
backgroundColor: 'rgba(0,0,0,.6)',
justifyContent: 'center',
overflow: 'hidden',
...StyleSheet.absoluteFillObject,
},
fullscreen: {
Expand Down Expand Up @@ -871,4 +929,25 @@ const controlStyle = StyleSheet.create({
timerText: {
textAlign: 'right',
},
doubleTap: {
position: 'absolute',
height: '100%',

},
rightDoubleTap: {
right: 0,

},
leftDoubleTap: {
left: 0,

},
leftDoubleTapContainer: {
borderTopRightRadius: width,
borderBottomRightRadius: width,
},
rightDoubleTapContainer: {
borderTopLeftRadius: width,
borderBottomLeftRadius: width,
}
});
12 changes: 12 additions & 0 deletions src/utils/hexToRgba.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function hexToRgbA(hex: string, opacity: number) {
let c: any;
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
c = hex.substring(1).split('');
if (c.length === 3) {
c = [c[0], c[0], c[1], c[1], c[2], c[2]];
}
c = `0x${c.join('')}`;
return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${opacity})`;
}
throw new Error('Bad Hex');
}

0 comments on commit b5e9163

Please sign in to comment.