import React, { useCallback, useEffect, useRef, useState } from "react"; import { StyleSheet, Text, View } from "react-native"; import { Palette, Style } from "../styles"; import { FONT_FAMILY } from "../styles/Fonts"; import { LinearGradient } from "./LinearGradient/LinearGradient"; const INITIAL_BOX_SIZE = 6; const HANDLE_SIZE = 20; const HANDLE_TOP_OFFSET = (INITIAL_BOX_SIZE - HANDLE_SIZE) / 2; const clamp01 = (value) => Math.min(1, Math.max(0, value)); const Slider = ({ value, maxValue, progress, onSeek, onSeekStart, onSeekEnd, seekEnabled = false, }) => { const [layoutWidth, setLayoutWidth] = useState(0); const [ratio, setRatio] = useState( typeof progress === "number" ? clamp01(progress) : 0, ); const draggingRef = useRef(false); useEffect(() => { if (!draggingRef.current && typeof progress === "number") { setRatio(clamp01(progress)); } }, [progress]); const updateRatioFromX = useCallback( (x) => { if (layoutWidth <= 0) return; const available = Math.max(layoutWidth - INITIAL_BOX_SIZE, 0); if (available <= 0) { setRatio(0); return; } const clampedX = Math.min(Math.max(x, 0), layoutWidth); const nextRatio = clamp01(clampedX / available); setRatio(nextRatio); }, [layoutWidth], ); const handleGrant = useCallback( (event) => { if (!seekEnabled) return; draggingRef.current = true; updateRatioFromX(event?.nativeEvent?.locationX || 0); if (typeof onSeekStart === "function") { onSeekStart(); } }, [seekEnabled, updateRatioFromX, onSeekStart], ); const handleMove = useCallback( (event) => { if (!seekEnabled || !draggingRef.current) return; updateRatioFromX(event?.nativeEvent?.locationX || 0); }, [seekEnabled, updateRatioFromX], ); const finishSeeking = useCallback(() => { if (!seekEnabled || !draggingRef.current) return; draggingRef.current = false; const currentRatio = clamp01(ratio); if (typeof onSeek === "function") { onSeek(currentRatio); } if (typeof onSeekEnd === "function") { onSeekEnd(); } }, [seekEnabled, ratio, onSeek, onSeekEnd]); const handleLayout = useCallback((event) => { const width = event?.nativeEvent?.layout?.width || 0; if (width !== layoutWidth) { setLayoutWidth(width); } }, [layoutWidth]); const maxOffset = Math.max(layoutWidth - INITIAL_BOX_SIZE, 0); const offset = maxOffset * ratio; return ( seekEnabled} onStartShouldSetResponder={() => seekEnabled} onMoveShouldSetResponder={() => seekEnabled} onResponderGrant={handleGrant} onResponderMove={handleMove} onResponderRelease={finishSeeking} onResponderTerminate={finishSeeking} > {value} {maxValue} ); }; const styles = StyleSheet.create({ container: { width: "100%", }, interactionArea: { width: "100%", height: HANDLE_SIZE, justifyContent: "center", position: "relative", }, pointerEnabled: { cursor: "pointer", }, sliderTrack: { width: "100%", height: INITIAL_BOX_SIZE, backgroundColor: "#0F0C19", borderRadius: 25, overflow: "hidden", }, box: { height: INITIAL_BOX_SIZE, borderRadius: 20, position: "absolute", left: 0, top: 0, zIndex: 1, overflow: "hidden", }, sliderHandle: { width: HANDLE_SIZE, height: HANDLE_SIZE, backgroundColor: "#f8f9ff", borderRadius: HANDLE_SIZE / 2, position: "absolute", top: HANDLE_TOP_OFFSET, zIndex: 2, borderWidth: 4, borderColor: "#9B4DFF", shadowColor: "#8951FC", shadowOffset: { width: 0, height: 3, }, shadowOpacity: 0.17, shadowRadius: 3.05, elevation: 4, }, time: { fontSize: 14, color: Palette.white, fontFamily: FONT_FAMILY.InterRegular, }, }); export default Slider;