import React, { useMemo, useState } from 'react'; import { I18nManager, StyleSheet, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { interpolate, runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; import { ProgressControlThumb, PROGRESS_THUMB_WIDTH } from './ProgressThumb'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { primitives } from '../../theme'; export type ProgressControlProps = { /** * If true, the underlying attachment is playing. */ isPlaying: boolean; /** * The progress of the progress bar in percentage */ progress: number; /** * The test ID of the progress control */ testID: string; /** * The function to be called when the user ends dragging the progress bar */ onEndDrag?: (progress: number) => void; /** * The function to be called when the user starts dragging the progress bar */ onStartDrag?: (progress: number) => void; /** * The width of the progress control */ width?: number; }; const TRACK_HEIGHT = 3; export const ProgressControl = (props: ProgressControlProps) => { const { isPlaying, onEndDrag, onStartDrag, progress, testID } = props; const styles = useStyles(); const [widthInNumbers, setWidthInNumbers] = useState(0); const isRTL = I18nManager.isRTL; const thumbDirectionMultiplier = isRTL ? -1 : 1; const state = useSharedValue(progress); const { theme: { progressControl: { container, filledStyles, thumb }, }, } = useTheme(); useAnimatedReaction( () => progress, (newProgress) => { state.value = newProgress; }, [progress], ); const pan = useMemo( () => Gesture.Pan() .maxPointers(1) .onStart(() => { if (onStartDrag) { runOnJS(onStartDrag)(state.value); } }) .onUpdate((event) => { const nextProgress = isRTL ? 1 - event.x / widthInNumbers : event.x / widthInNumbers; state.value = Math.max(0, Math.min(nextProgress, 1)); }) .onEnd(() => { if (onEndDrag) { runOnJS(onEndDrag)(state.value); } }) .withTestId(testID), [isRTL, onEndDrag, onStartDrag, state, testID, widthInNumbers], ); const thumbStyles = useAnimatedStyle( () => ({ transform: [ { translateX: interpolate( state.value, [0, 1], [0, (widthInNumbers - PROGRESS_THUMB_WIDTH / 2) * thumbDirectionMultiplier], ), }, ], position: 'absolute', }), [thumbDirectionMultiplier, widthInNumbers], ); const animatedFilledStyles = useAnimatedStyle( () => ({ width: state.value * widthInNumbers, }), [widthInNumbers], ); return ( { setWidthInNumbers(nativeEvent.layout.width); }} style={[styles.container, container]} > {onEndDrag ? : null} ); }; const useStyles = () => { const { theme: { semantics }, } = useTheme(); return useMemo(() => { return StyleSheet.create({ container: { borderRadius: primitives.radiusMax, height: TRACK_HEIGHT, alignItems: 'center', flexDirection: 'row', backgroundColor: semantics.chatWaveformBar, }, filledStyle: { alignSelf: 'center', borderRadius: primitives.radiusMax, height: TRACK_HEIGHT, backgroundColor: semantics.chatWaveformBarPlaying, }, }); }, [semantics.chatWaveformBar, semantics.chatWaveformBarPlaying]); }; ProgressControl.displayName = 'ProgressControl';