second flow

This commit is contained in:
2025-10-01 16:49:12 +02:00
parent efc84ce2ab
commit 1fd63c254d
8 changed files with 505 additions and 65 deletions
+182
View File
@@ -0,0 +1,182 @@
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 (
<View style={styles.container}>
<View
style={[
styles.interactionArea,
seekEnabled ? styles.pointerEnabled : null,
]}
onLayout={handleLayout}
onStartShouldSetResponderCapture={() => seekEnabled}
onStartShouldSetResponder={() => seekEnabled}
onMoveShouldSetResponder={() => seekEnabled}
onResponderGrant={handleGrant}
onResponderMove={handleMove}
onResponderRelease={finishSeeking}
onResponderTerminate={finishSeeking}
>
<View style={styles.sliderTrack}>
<View style={[styles.box, { width: INITIAL_BOX_SIZE + offset }]}>
<LinearGradient
colors={["#F94697", "#7023F7"]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={{ flex: 1, borderRadius: 20 }}
/>
</View>
</View>
<View style={[styles.sliderHandle, { left: offset }]} />
</View>
<View style={{ ...Style.containerSpaceBetween, marginTop: 10 }}>
<Text style={styles.time}>{value}</Text>
<Text style={styles.time}>{maxValue}</Text>
</View>
</View>
);
};
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;