- 添加 Redux 状态管理,支持用户登录和个人信息的持久化 - 新增目标管理页面,允许用户设置每日卡路里和步数目标 - 更新首页,移除旧的活动展示,改为固定的热点功能卡片 - 修改布局以适应新功能的展示和交互 - 更新依赖,添加 @reduxjs/toolkit 和 react-redux 库以支持状态管理 - 新增 API 服务模块,处理与后端的交互
100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { Animated, Easing, LayoutChangeEvent, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
|
|
|
type ProgressBarProps = {
|
|
progress: number; // 0 - 1
|
|
height?: number;
|
|
style?: ViewStyle;
|
|
trackColor?: string;
|
|
fillColor?: string;
|
|
animated?: boolean;
|
|
showLabel?: boolean;
|
|
};
|
|
|
|
function ProgressBarImpl({
|
|
progress,
|
|
height = 18,
|
|
style,
|
|
trackColor = '#EDEDED',
|
|
fillColor = '#BBF246',
|
|
animated = true,
|
|
showLabel = true,
|
|
}: ProgressBarProps) {
|
|
const [trackWidth, setTrackWidth] = useState(0);
|
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
|
|
const clamped = useMemo(() => {
|
|
if (Number.isNaN(progress)) return 0;
|
|
return Math.min(1, Math.max(0, progress));
|
|
}, [progress]);
|
|
|
|
useEffect(() => {
|
|
if (!animated) {
|
|
animatedValue.setValue(clamped);
|
|
return;
|
|
}
|
|
Animated.timing(animatedValue, {
|
|
toValue: clamped,
|
|
duration: 650,
|
|
easing: Easing.out(Easing.cubic),
|
|
useNativeDriver: false,
|
|
}).start();
|
|
}, [clamped, animated, animatedValue]);
|
|
|
|
const onLayout = (e: LayoutChangeEvent) => {
|
|
setTrackWidth(e.nativeEvent.layout.width);
|
|
};
|
|
|
|
const fillWidth = Animated.multiply(animatedValue, trackWidth || 1);
|
|
const percent = Math.round(clamped * 100);
|
|
|
|
return (
|
|
<View style={[styles.container, { height }, style]} onLayout={onLayout}>
|
|
<View style={[styles.track, { backgroundColor: trackColor }]} />
|
|
<Animated.View style={[styles.fill, { width: fillWidth, backgroundColor: fillColor }]}>
|
|
{showLabel && (
|
|
<Text style={styles.label}>{percent}%</Text>
|
|
)}
|
|
</Animated.View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export const ProgressBar = memo(ProgressBarImpl, (prev, next) => {
|
|
// 仅在这些关键属性变化时重新渲染,忽略 style 的引用变化
|
|
return (
|
|
prev.progress === next.progress &&
|
|
prev.height === next.height &&
|
|
prev.trackColor === next.trackColor &&
|
|
prev.fillColor === next.fillColor &&
|
|
prev.animated === next.animated &&
|
|
prev.showLabel === next.showLabel
|
|
);
|
|
});
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
width: '100%',
|
|
borderRadius: 10,
|
|
overflow: 'hidden',
|
|
backgroundColor: 'transparent',
|
|
},
|
|
track: {
|
|
...StyleSheet.absoluteFillObject,
|
|
borderRadius: 10,
|
|
},
|
|
fill: {
|
|
height: '100%',
|
|
borderRadius: 10,
|
|
justifyContent: 'center',
|
|
paddingHorizontal: 10,
|
|
},
|
|
label: {
|
|
color: '#192126',
|
|
fontWeight: '700',
|
|
fontSize: 12,
|
|
},
|
|
});
|
|
|
|
|