import React, { memo } from 'react' import { useWindowDimensions, type StyleProp, type ViewStyle, View, type ColorValue, type ViewProps, } from 'react-native' import Animated, { useSharedValue, type SharedValue, useAnimatedStyle, withDecay, interpolate, Extrapolation, } from 'react-native-reanimated' import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler' import type { Metering } from '../Recorder.types' import { METERING_MAX_POWER, METERING_MIN_POWER, TIMELINE_MS_PER_LINE, WAVEFORM_LINE_WIDTH, Spacing, } from '../helpers' import { TimeIndicator } from './TimeIndicator' import { Timeline } from './Timeline' const DEFAULT_WAVEFORM_ACTIVE_COLOR = '#d72d66' const DEFAULT_WAVEFORM_INACTIVE_COLOR = 'rgba(0, 0, 0, 0.4)' const DEFAULT_WAVEFORM_HEIGHT = 160 const DEFAULT_BACKGROUND_COLOR = '#f9f9f9' const DEFAULT_PROGRESS_BACKGROUND_COLOR = '#bbbbbb' interface WaveformProps extends ViewProps { meterings: Metering[] maxDuration: number recording: boolean playing: boolean waveformMaxWidth: number timelineGap: number timelineColor?: ColorValue waveformHeight?: number waveformActiveColor?: ColorValue waveformInactiveColor?: ColorValue backgroundColor?: ColorValue progressBackgroundColor?: ColorValue tintColor?: ColorValue scrollX: SharedValue } interface WaveformLineProps { position: number db: number color: ColorValue maxHeight: number gap: number } export const Waveform = memo((props: WaveformProps) => { const { scrollX, meterings = [], recording, playing, backgroundColor = DEFAULT_BACKGROUND_COLOR, progressBackgroundColor = DEFAULT_PROGRESS_BACKGROUND_COLOR, maxDuration, tintColor, timelineColor, timelineGap, waveformMaxWidth, waveformHeight = DEFAULT_WAVEFORM_HEIGHT, waveformActiveColor = DEFAULT_WAVEFORM_ACTIVE_COLOR, waveformInactiveColor = DEFAULT_WAVEFORM_INACTIVE_COLOR, style, } = props const dimensions = useWindowDimensions() const waveformContainerHeight = waveformHeight + Spacing.md * 2 const prevScrollX = useSharedValue(0) const $waveformWrapper: StyleProp = [ { height: waveformContainerHeight, left: dimensions.width / 2 - WAVEFORM_LINE_WIDTH / 2, }, style, useAnimatedStyle(() => ({ transform: [ { translateX: scrollX.value, }, ], })), ] const $waveformLineStyles: StyleProp = [ $waveformLines, { backgroundColor: progressBackgroundColor, height: waveformContainerHeight, width: waveformMaxWidth, }, ] const pan = Gesture.Pan() .onBegin(() => { prevScrollX.value = scrollX.value }) .onChange((e) => { const x = prevScrollX.value + e.translationX scrollX.value = x }) .onFinalize((e) => { scrollX.value = withDecay({ velocity: e.velocityX, deceleration: 0.995, rubberBandEffect: true, rubberBandFactor: 1, clamp: [-waveformMaxWidth, 0], }) }) .activeOffsetX([-10, 10]) .enabled(!recording && !playing) return ( {meterings.map(({ position, key, db }) => ( ))} {recording && ( )} ) }) const WaveformLine = memo((props: WaveformLineProps) => { const { db, maxHeight, gap, color, position } = props return ( ) }) const $gestureHandler: ViewStyle = { flexGrow: 1, } const $waveformLines: ViewStyle = { position: 'absolute', flexDirection: 'row', alignItems: 'center', } const $waveformLine: ViewStyle = { position: 'absolute', width: WAVEFORM_LINE_WIDTH, } const $waveformBackground: ViewStyle = { position: 'absolute', left: 0, right: 0, } const $progressCover: ViewStyle = { position: 'absolute', backgroundColor: 'red', right: 0, }