import React from 'react'; import { LayoutChangeEvent, View, TextInput, TextInputProps } from 'react-native'; import { PanGestureHandler } from 'react-native-gesture-handler'; import Animated, { SharedValue, runOnJS, useAnimatedGestureHandler, useAnimatedProps, useAnimatedStyle, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import styles from './styles'; import { useTheme } from '../../theme'; import { AUDIO_BUTTON_HIT_SLOP, THUMB_SEEK_SIZE } from './utils'; const DEFAULT_TIME_LABEL = '00:00'; const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); interface ISeek { duration: SharedValue; currentTime: SharedValue; loaded: boolean; onChangeTime: (time: number) => Promise; } // Could use from react-native-redash. We have it as dev dep already. function clamp(value: number, min: number, max: number) { 'worklet'; return Math.min(Math.max(value, min), max); } const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => { const { colors } = useTheme(); const maxWidth = useSharedValue(1); const timePosition = useSharedValue(0); const timeLabel = useSharedValue(DEFAULT_TIME_LABEL); const scale = useSharedValue(1); const isTimeChanged = useSharedValue(false); const styleLine = useAnimatedStyle(() => ({ width: timePosition.value })); const styleThumb = useAnimatedStyle(() => ({ transform: [{ translateX: timePosition.value }, { scale: scale.value }] })); const onLayout = (event: LayoutChangeEvent) => { const { width } = event.nativeEvent.layout; maxWidth.value = width - THUMB_SEEK_SIZE; }; const onGestureEvent = useAnimatedGestureHandler({ onStart: (_, ctx: any) => { ctx.startX = timePosition.value; }, onActive: (event, ctx: any) => { timePosition.value = clamp(ctx.startX + event.translationX, 0, maxWidth.value); isTimeChanged.value = true; scale.value = 1.3; currentTime.value = (timePosition.value * duration.value) / maxWidth.value || 0; }, onEnd: () => { scale.value = 1; runOnJS(onChangeTime)(Math.round(currentTime.value * 1000)); } }); // https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/ const formatTime = (ms: number) => { 'worklet'; const minutes = Math.floor(ms / 60); const remainingSeconds = Math.floor(ms % 60); const formattedMinutes = String(minutes).padStart(2, '0'); const formattedSeconds = String(remainingSeconds).padStart(2, '0'); return `${formattedMinutes}:${formattedSeconds}`; }; useDerivedValue(() => { const timeInProgress = (currentTime.value * maxWidth.value) / duration.value || 0; timePosition.value = timeInProgress; if (currentTime.value !== 0) { isTimeChanged.value = true; } timeLabel.value = formatTime(currentTime.value); }, [timePosition, maxWidth, duration, currentTime]); const getCurrentTime = useAnimatedProps(() => { if (isTimeChanged.value) { return { text: timeLabel.value } as TextInputProps; } return { text: formatTime(duration.value) } as TextInputProps; }, [timeLabel, isTimeChanged, duration]); const thumbColor = loaded ? colors.buttonBackgroundPrimaryDefault : colors.tintDisabled; return ( ); }; export default Seek;