import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Animated, Easing, StyleSheet, Text, View, ViewStyle } from 'react-native'; // 主题色 const THEME_PRIMARY = '#4F5BD5'; const THEME_SECONDARY = '#6B6CFF'; const THEME_SUCCESS = '#22C55E'; const THEME_TEXT_SECONDARY = '#6f7ba7'; export interface WeightProgressBarProps { /** 进度值 0-1 */ progress: number; /** 当前体重 */ currentWeight: number; /** 目标体重 */ targetWeight: number; /** 初始体重 */ initialWeight: number; /** 容器样式 */ style?: ViewStyle; /** 是否显示顶部分隔线,默认 true */ showTopBorder?: boolean; } export const WeightProgressBar: React.FC = ({ progress, currentWeight, targetWeight, initialWeight, style, showTopBorder = true, }) => { const { t } = useTranslation(); const animatedProgress = useRef(new Animated.Value(0)).current; const [barWidth, setBarWidth] = useState(0); const clampedProgress = Math.min(1, Math.max(0, progress)); const percent = Math.round(clampedProgress * 100); // 判断是否有有效数据 const hasInitialWeight = initialWeight > 0; const hasTargetWeight = targetWeight > 0; const hasCurrentWeight = currentWeight > 0; // 只要有初始体重和当前体重,就可以显示已减重量 const canShowLost = hasInitialWeight && hasCurrentWeight; // 需要有目标体重才能显示距离目标和进度 const canShowTarget = hasTargetWeight && hasCurrentWeight; useEffect(() => { // 延迟 500ms 开始动画,避免页面刚进入时卡顿 const timer = setTimeout(() => { Animated.timing(animatedProgress, { toValue: clampedProgress, duration: 800, easing: Easing.out(Easing.cubic), useNativeDriver: false, }).start(); }, 800); return () => clearTimeout(timer); }, [clampedProgress]); const fillWidth = animatedProgress.interpolate({ inputRange: [0, 1], outputRange: [0, barWidth], }); const sliderPosition = animatedProgress.interpolate({ inputRange: [0, 1], outputRange: [-12, barWidth - 12], }); const weightLost = initialWeight - currentWeight; const weightToGo = currentWeight - targetWeight; return ( {/* 进度信息 */} {t('statistics.components.weight.progress.lost')} = 0 ? THEME_SUCCESS : (canShowLost ? '#FF6B6B' : THEME_TEXT_SECONDARY) }]}> {canShowLost ? `${weightLost >= 0 ? '-' : '+'}${Math.abs(weightLost).toFixed(1)}kg` : '--'} {percent} % {t('statistics.components.weight.progress.toGo')} {canShowTarget ? `${weightToGo > 0 ? weightToGo.toFixed(1) : '0'}kg` : '--'} {/* 进度条 */} setBarWidth(e.nativeEvent.layout.width)} > {/* 背景轨道 */} {/* 填充进度 */} {/* 滑块 - 圆角矩形 */} {/* 起止标签 */} {hasInitialWeight ? `${initialWeight.toFixed(1)}kg` : '--'} {hasTargetWeight ? `${targetWeight.toFixed(1)}kg` : '--'} ); }; const styles = StyleSheet.create({ container: { marginTop: 12, paddingTop: 10, marginLeft:12, marginRight: 12 }, topBorder: { borderTopWidth: 1, borderTopColor: 'rgba(0,0,0,0.04)', }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, infoItem: { flex: 1, }, infoLabel: { fontSize: 11, color: THEME_TEXT_SECONDARY, fontFamily: 'AliRegular', marginBottom: 2, }, infoValue: { fontSize: 14, fontWeight: '700', fontFamily: 'AliBold', }, percentContainer: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'center', }, percentValue: { fontSize: 24, fontWeight: '800', color: THEME_PRIMARY, fontFamily: 'AliBold', }, percentSymbol: { fontSize: 12, fontWeight: '600', color: THEME_PRIMARY, fontFamily: 'AliBold', marginLeft: 2, }, trackContainer: { height: 8, position: 'relative', marginBottom: 8, }, track: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, backgroundColor: '#E8EAF0', borderRadius: 4, }, fill: { position: 'absolute', left: 0, top: 0, bottom: 0, borderRadius: 4, overflow: 'hidden', }, slider: { position: 'absolute', top: -8, width: 24, height: 24, borderRadius: 8, shadowColor: THEME_PRIMARY, shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.35, shadowRadius: 6, elevation: 6, }, sliderInner: { width: '100%', height: '100%', borderRadius: 8, alignItems: 'center', justifyContent: 'center', borderWidth: 2.5, borderColor: THEME_PRIMARY, }, sliderLine: { width: 8, height: 3, borderRadius: 1.5, backgroundColor: THEME_PRIMARY, }, labelRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, labelText: { fontSize: 11, color: THEME_TEXT_SECONDARY, fontFamily: 'AliRegular', }, targetBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(79, 91, 213, 0.1)', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 10, gap: 4, }, targetText: { fontSize: 11, color: THEME_PRIMARY, fontWeight: '600', fontFamily: 'AliBold', }, }); export default WeightProgressBar;