import React from 'react'; import { Svg, Defs, LinearGradient, Stop, G, Circle, } from 'react-native-svg'; import { StyleProp, ViewStyle, View, Animated, Text, TextStyle } from 'react-native'; import { clamp } from 'common/utils/mathx'; import Colors from 'src/constants/colors'; import TextStyles from 'src/styles/TextStyles'; const AnimatedContainer = Animated.createAnimatedComponent(Circle); type Props = { style?: StyleProp<ViewStyle>; progress: number; diameter: number; }; const getOffset = (d: number, progress: number) => Math.round(d * Math.PI * clamp(1 - progress, 0, 1)); export function ProgressBarCircle(this: never, props: Props) { const { style, diameter, progress } = props; const length = Math.round(diameter * Math.PI); const offset = getOffset(diameter, progress); return ( <View style={style}> <Svg id="progress-bar-circle" width="100%" height="100%" viewBox={`0 0 ${diameter + 2} ${diameter + 2}`} fill="none"> <G x={1} y={1}> <Circle opacity="0.2" cx={diameter / 2} cy={diameter / 2} r={diameter / 2} stroke={Colors.ProgressBarCircle.progressBarCircleLine} strokeWidth="1" /> <Circle cx={diameter / 2} cy={diameter / 2} r={diameter / 2} stroke={Colors.ProgressBarCircle.progressBarCircleLine} strokeWidth="1" strokeDasharray={length} strokeDashoffset={offset} origin={`${diameter / 2}, ${diameter / 2}`} rotation={-90} /> </G> </Svg> </View> ); } export type GradColor = { title: string, offset: number, }; type PropsGradient = Props & { title: string, animationStep: number, gradient?: GradColor[], titleStyle?: StyleProp<any>, startFromBottom?: boolean, }; export function ProgressBarCircleGradient(this: never, props: PropsGradient) { const { animationStep, title, diameter, gradient, progress, style, titleStyle, startFromBottom } = props; const length = Math.round(diameter * Math.PI); const [ offset ] = React.useState(() => { const startProgress = progress - (animationStep || 0.1); return new Animated.Value(getOffset(diameter, startProgress)); }); React.useEffect(() => { Animated.timing(offset, { toValue: getOffset(diameter, progress), duration: 2000, delay: 750, }).start(); }, [progress]); const boxSize = diameter + 6; const origin = diameter / 2; const defaultTitleStyles: StyleProp<TextStyle> = { position: 'absolute', alignItems: 'center', justifyContent: 'center', }; return ( <View style={style}> <Text style={[TextStyles.p3, defaultTitleStyles, titleStyle]}>{title}</Text> <Svg id="progress-bar-circle" width="100%" height="100%" viewBox={`0 0 ${boxSize} ${boxSize}`} fill="none"> <G x={3} y={3}> <Circle id="circle-bg" opacity="0.2" cx={diameter / 2} cy={diameter / 2} r={diameter / 2} strokeWidth="3" stroke="url(#paint0_linear)" fill="none" /> <AnimatedContainer id="circle-progress" fill="none" cx={diameter / 2} cy={diameter / 2} r={diameter / 2} strokeWidth="3" stroke="url(#paint0_linear)" strokeDasharray={length} strokeDashoffset={offset} origin={`${origin}, ${origin}`} rotation={startFromBottom ? 90 : -90} /> </G> <Defs> {gradient ? ( <LinearGradient id="paint0_linear" x1={diameter / 2} y1={diameter} x2={diameter} y2={0} gradientUnits="userSpaceOnUse"> {gradient.map((color, i) => { // tslint:disable-next-line: no-unused-expression return <Stop key={`${color.title}_${i}`} offset={color.offset} stopColor={color.title}/>; })} </LinearGradient> ) : ( <LinearGradient id="paint0_linear" x1={0} y1={diameter / 2} x2={diameter} y2={diameter / 2} gradientUnits="userSpaceOnUse"> <Stop offset="0.0996095" stopColor="#FFCECF"/> <Stop offset="0.238673" stopColor="#9A87E7"/> <Stop offset="0.384948" stopColor="#FFAFD5"/> <Stop offset="0.512681" stopColor="#FA8989"/> <Stop offset="0.642474" stopColor="#CFC4FF"/> <Stop offset="0.776388" stopColor="#FFAFD5"/> <Stop offset="0.90206" stopColor="#FFCED0"/> </LinearGradient> )} </Defs> </Svg> </View> ); }