import React, { useEffect } from "react"; import { StyleSheet, View, TextInput, Text } from "react-native"; import Svg, { G, Circle, Defs, LinearGradient, Stop } from "react-native-svg"; import Animated, { useSharedValue, withTiming, useAnimatedProps, withDelay, runOnJS, useDerivedValue, } from "react-native-reanimated"; import { CircularProgressProps } from "./types"; const AnimatedCircle = Animated.createAnimatedComponent(Circle); const AnimatedInput = Animated.createAnimatedComponent(TextInput); const CircularProgress: React.FC = ({ value, initialValue = 0, title = "", titleStyle = {}, titleColor, titleFontSize, circleBackgroundColor = "transparent", radius = 60, duration = 500, delay = 0, textColor, textStyle = {}, fontSize, maxValue = 100, strokeLinecap = "round", onAnimationComplete = () => {}, valuePrefix = "", valueSuffix = "", activeStrokeColor = "#2ecc71", activeStrokeSecondaryColor = "", activeStrokeWidth = 10, inActiveStrokeColor = "rgba(0,0,0,0.3)", inActiveStrokeWidth = 10, inActiveStrokeOpacity = 1, showProgressValue = true, clockwise = true, subtitle = "", subtitleStyle = {}, subtitleColor, subtitleFontSize, }) => { const styleProps = { radius, textColor, fontSize, textStyle, activeStrokeColor, titleStyle, titleColor, titleFontSize, showProgressValue, subtitleColor, subtitleFontSize, }; const animatedValue = useSharedValue(initialValue); const viewBox = radius + Math.max(activeStrokeWidth, inActiveStrokeWidth); const circleCircumference = 2 * Math.PI * radius; const animatedCircleProps = useAnimatedProps(() => { let biggestValue = Math.max(initialValue, maxValue); biggestValue = biggestValue <= 0 ? 1 : biggestValue; const maxPercentage: number = clockwise ? (100 * animatedValue.value) / biggestValue : (100 * -animatedValue.value) / biggestValue; return { strokeDashoffset: circleCircumference - (circleCircumference * maxPercentage) / 100, }; }); const progressValue = useDerivedValue(() => { return `${valuePrefix}${Math.round(animatedValue.value)}${valueSuffix}`; }); const animatedTextProps = useAnimatedProps(() => { return { text: progressValue.value, } as any; }); useEffect(() => { animatedValue.value = withDelay( delay, withTiming(value, { duration }, (isFinished) => { if (isFinished) { runOnJS(onAnimationComplete)?.(); } }) ); }, [value]); return ( {activeStrokeSecondaryColor ? ( ) : null} {showProgressValue && ( )} {title && title !== "" ? ( {title} ) : null} {subtitle && subtitle !== "" ? ( {subtitle} ) : null} ); }; export const dynamicStyles = (props: any) => { return StyleSheet.create({ fromProps: { fontSize: props.fontSize || props.textStyle?.fontSize || props.radius / 2, color: props.textColor || props.textStyle?.color || props.activeStrokeColor, }, input: { fontWeight: "bold", textAlign: "center", }, valueContainer: { flex: 1, alignItems: "center", justifyContent: "center", }, title: { textAlign: "center", width: "70%", marginTop: props.showProgressValue ? props.radius * 0.05 : 0, color: props.titleColor || props.titleStyle?.color || props.activeStrokeColor, fontSize: props.titleFontSize || props.titleStyle?.fontSize || props.fontSize || props.radius / 4, }, subtitle: { color: props.subtitleColor || props.subtitleStyle?.color || props.activeStrokeColor, fontSize: props.subtitleFontSize || props.subtitleStyle?.fontSize || props.fontSize || props.radius / 5, }, }); }; export default CircularProgress;