148 lines
3.9 KiB
TypeScript
148 lines
3.9 KiB
TypeScript
import React, { useEffect, useRef, useState } from "react";
|
|
import { View, StyleSheet, Text, Animated } from "react-native";
|
|
import Svg, { Circle, Defs, LinearGradient, Stop } from "react-native-svg";
|
|
|
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
|
|
const CircleProgressBar = ({
|
|
progress,
|
|
status,
|
|
}: {
|
|
progress: number;
|
|
status: number;
|
|
}) => {
|
|
const radius = 20;
|
|
const strokeWidth = 5;
|
|
const viewBoxPadding = 4;
|
|
const viewBoxSize = (radius + strokeWidth) * 2 + viewBoxPadding;
|
|
const center = viewBoxSize / 2;
|
|
const circumference = 2 * Math.PI * radius;
|
|
|
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
const [displayProgress, setDisplayProgress] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (progress === undefined) {
|
|
animatedValue.setValue(0);
|
|
setDisplayProgress(0);
|
|
} else {
|
|
Animated.timing(animatedValue, {
|
|
toValue: progress,
|
|
duration: 500,
|
|
useNativeDriver: false,
|
|
}).start();
|
|
}
|
|
}, [progress]);
|
|
|
|
useEffect(() => {
|
|
const listenerId = animatedValue.addListener(({ value }) => {
|
|
setDisplayProgress(Math.round(value));
|
|
});
|
|
return () => animatedValue.removeListener(listenerId);
|
|
}, []);
|
|
|
|
const getColor = (progress: number) => {
|
|
if (progress <= 20) return "#D51D10";
|
|
if (progress <= 50) return "#FF7B00";
|
|
return "#009E71";
|
|
};
|
|
|
|
const animatedColor = getColor(displayProgress);
|
|
|
|
const dashOffset = animatedValue.interpolate({
|
|
inputRange: [0, 100],
|
|
outputRange: [circumference, circumference - 0.7 * circumference],
|
|
});
|
|
|
|
const progressDashArray = `${circumference} ${circumference}`;
|
|
const backgroundDashArray = `${0.7 * circumference} ${0.3 * circumference}`;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Svg
|
|
width={258.75}
|
|
height={258.75}
|
|
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
|
style={styles.circleContainer}
|
|
>
|
|
<Defs>
|
|
<LinearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<Stop offset="0%" stopColor="#e66465" />
|
|
<Stop offset="100%" stopColor="#9198e5" />
|
|
</LinearGradient>
|
|
</Defs>
|
|
<Circle
|
|
r={radius}
|
|
cx={center}
|
|
cy={center}
|
|
stroke="#D8DDE7"
|
|
strokeWidth={strokeWidth}
|
|
fill="none"
|
|
strokeDasharray={backgroundDashArray}
|
|
strokeLinecap="round"
|
|
/>
|
|
<AnimatedCircle
|
|
r={radius}
|
|
cx={center}
|
|
cy={center}
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
style={{
|
|
stroke: animatedColor,
|
|
strokeWidth: strokeWidth,
|
|
strokeDasharray: progressDashArray,
|
|
strokeDashoffset: dashOffset,
|
|
}}
|
|
/>
|
|
</Svg>
|
|
<View style={styles.batteryPercent}>
|
|
<Text style={{ fontSize: 60, fontWeight: "bold", fontFamily: "Inter" }}>
|
|
{progress === undefined ? "---" : displayProgress}
|
|
</Text>
|
|
<Text style={{ fontSize: 20, fontWeight: "bold" }}>
|
|
{progress === undefined ? null : "%"}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.batterySoc}>
|
|
<Text style={{ fontSize: 14, fontWeight: "bold", color: "#565F70" }}>
|
|
{status === 1 ? "Charging" : "Discharging"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// Keep the styles the same as before
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
position: "relative",
|
|
alignItems: "center",
|
|
},
|
|
circleContainer: {
|
|
transform: [{ rotate: "144deg" }],
|
|
},
|
|
batteryPercent: {
|
|
position: "absolute",
|
|
top: 80,
|
|
justifyContent: "center",
|
|
alignItems: "baseline",
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
},
|
|
batterySoc: {
|
|
position: "absolute",
|
|
top: 210,
|
|
justifyContent: "center",
|
|
transform: [{ translateX: 0 }],
|
|
},
|
|
batteryStatus: {
|
|
position: "absolute",
|
|
top: 300,
|
|
justifyContent: "center",
|
|
transform: [{ translateX: 0 }],
|
|
},
|
|
});
|
|
|
|
export default CircleProgressBar;
|